From fc9401f04a3aca5abb22f87ebc210de8afe11d32 Mon Sep 17 00:00:00 2001 From: marvin-borner@live.com Date: Tue, 10 Apr 2018 21:50:16 +0200 Subject: Initial Commit --- assets/css/main.css | 172 ++ assets/icons/BurgerMenuShort.svg | 57 + assets/icons/ExploreGlobe.svg | 210 ++ assets/icons/ExploreGlobeOutline.svg | 210 ++ assets/icons/FriendFeedOutline.svg | 82 + assets/icons/MessageBubble.svg | 73 + assets/icons/MessageBubbleOutline.svg | 73 + assets/icons/UserGroupOutline.svg | 80 + assets/icons/UserOutline.svg | 54 + assets/js/chat.js | 33 + assets/js/main.js | 93 + assets/php/bin/WebChatServer.php | 18 + assets/php/bin/server.csr | 18 + assets/php/composer.json | 11 + assets/php/composer.lock | 943 ++++++++ assets/php/src/ChatProcessor.php | 93 + assets/php/vendor/autoload.php | 7 + assets/php/vendor/cboden/ratchet/.gitignore | 5 + assets/php/vendor/cboden/ratchet/.travis.yml | 20 + assets/php/vendor/cboden/ratchet/CHANGELOG.md | 135 ++ assets/php/vendor/cboden/ratchet/LICENSE | 19 + assets/php/vendor/cboden/ratchet/Makefile | 42 + assets/php/vendor/cboden/ratchet/README.md | 83 + assets/php/vendor/cboden/ratchet/composer.json | 36 + assets/php/vendor/cboden/ratchet/phpunit.xml.dist | 30 + .../src/Ratchet/AbstractConnectionDecorator.php | 41 + .../php/vendor/cboden/ratchet/src/Ratchet/App.php | 145 ++ .../ratchet/src/Ratchet/ComponentInterface.php | 31 + .../ratchet/src/Ratchet/ConnectionInterface.php | 26 + .../src/Ratchet/Http/CloseResponseTrait.php | 22 + .../ratchet/src/Ratchet/Http/HttpRequestParser.php | 64 + .../cboden/ratchet/src/Ratchet/Http/HttpServer.php | 76 + .../src/Ratchet/Http/HttpServerInterface.php | 14 + .../src/Ratchet/Http/NoOpHttpServerController.php | 18 + .../ratchet/src/Ratchet/Http/OriginCheck.php | 65 + .../cboden/ratchet/src/Ratchet/Http/Router.php | 96 + .../src/Ratchet/MessageComponentInterface.php | 5 + .../ratchet/src/Ratchet/MessageInterface.php | 12 + .../ratchet/src/Ratchet/Server/EchoServer.php | 23 + .../ratchet/src/Ratchet/Server/FlashPolicy.php | 200 ++ .../ratchet/src/Ratchet/Server/IoConnection.php | 38 + .../cboden/ratchet/src/Ratchet/Server/IoServer.php | 140 ++ .../ratchet/src/Ratchet/Server/IpBlackList.php | 111 + .../Ratchet/Session/Serialize/HandlerInterface.php | 16 + .../Ratchet/Session/Serialize/PhpBinaryHandler.php | 33 + .../src/Ratchet/Session/Serialize/PhpHandler.php | 49 + .../src/Ratchet/Session/SessionProvider.php | 243 ++ .../Ratchet/Session/Storage/Proxy/VirtualProxy.php | 54 + .../Session/Storage/VirtualSessionStorage.php | 88 + .../cboden/ratchet/src/Ratchet/Wamp/Exception.php | 5 + .../ratchet/src/Ratchet/Wamp/JsonException.php | 31 + .../ratchet/src/Ratchet/Wamp/ServerProtocol.php | 161 ++ .../cboden/ratchet/src/Ratchet/Wamp/Topic.php | 99 + .../ratchet/src/Ratchet/Wamp/TopicManager.php | 125 ++ .../ratchet/src/Ratchet/Wamp/WampConnection.php | 115 + .../cboden/ratchet/src/Ratchet/Wamp/WampServer.php | 67 + .../src/Ratchet/Wamp/WampServerInterface.php | 43 + .../ratchet/src/Ratchet/WebSocket/ConnContext.php | 20 + .../Ratchet/WebSocket/MessageCallableInterface.php | 8 + .../WebSocket/MessageComponentInterface.php | 6 + .../ratchet/src/Ratchet/WebSocket/WsConnection.php | 45 + .../ratchet/src/Ratchet/WebSocket/WsServer.php | 225 ++ .../src/Ratchet/WebSocket/WsServerInterface.php | 14 + .../ratchet/tests/autobahn/bin/fuzzingserver.php | 36 + .../ratchet/tests/autobahn/fuzzingclient-all.json | 15 + .../tests/autobahn/fuzzingclient-profile.json | 12 + .../tests/autobahn/fuzzingclient-quick.json | 12 + .../php/vendor/cboden/ratchet/tests/bootstrap.php | 4 + .../Ratchet/AbstractMessageComponentTestCase.php | 50 + .../tests/helpers/Ratchet/Mock/Component.php | 35 + .../tests/helpers/Ratchet/Mock/Connection.php | 20 + .../helpers/Ratchet/Mock/ConnectionDecorator.php | 22 + .../tests/helpers/Ratchet/Mock/WampComponent.php | 43 + .../tests/helpers/Ratchet/NullComponent.php | 28 + .../Ratchet/Wamp/Stub/WsWampServerInterface.php | 7 + .../WebSocket/Stub/WsMessageComponentInterface.php | 7 + .../tests/unit/AbstractConnectionDecoratorTest.php | 147 ++ .../tests/unit/Http/HttpRequestParserTest.php | 50 + .../ratchet/tests/unit/Http/HttpServerTest.php | 64 + .../ratchet/tests/unit/Http/OriginCheckTest.php | 46 + .../cboden/ratchet/tests/unit/Http/RouterTest.php | 165 ++ .../ratchet/tests/unit/Server/EchoServerTest.php | 26 + .../tests/unit/Server/FlashPolicyComponentTest.php | 152 ++ .../ratchet/tests/unit/Server/IoConnectionTest.php | 32 + .../ratchet/tests/unit/Server/IoServerTest.php | 118 + .../tests/unit/Server/IpBlackListComponentTest.php | 125 ++ .../unit/Session/Serialize/PhpHandlerTest.php | 43 + .../tests/unit/Session/SessionComponentTest.php | 124 ++ .../Storage/VirtualSessionStoragePDOTest.php | 53 + .../ratchet/tests/unit/Wamp/ServerProtocolTest.php | 295 +++ .../ratchet/tests/unit/Wamp/TopicManagerTest.php | 226 ++ .../cboden/ratchet/tests/unit/Wamp/TopicTest.php | 164 ++ .../ratchet/tests/unit/Wamp/WampConnectionTest.php | 77 + .../ratchet/tests/unit/Wamp/WampServerTest.php | 49 + assets/php/vendor/composer/ClassLoader.php | 445 ++++ assets/php/vendor/composer/LICENSE | 21 + assets/php/vendor/composer/autoload_classmap.php | 16 + assets/php/vendor/composer/autoload_files.php | 15 + assets/php/vendor/composer/autoload_namespaces.php | 10 + assets/php/vendor/composer/autoload_psr4.php | 26 + assets/php/vendor/composer/autoload_real.php | 70 + assets/php/vendor/composer/autoload_static.php | 157 ++ assets/php/vendor/composer/installed.json | 963 ++++++++ assets/php/vendor/evenement/evenement/.gitignore | 2 + assets/php/vendor/evenement/evenement/.travis.yml | 24 + assets/php/vendor/evenement/evenement/CHANGELOG.md | 35 + assets/php/vendor/evenement/evenement/LICENSE | 19 + assets/php/vendor/evenement/evenement/README.md | 83 + .../php/vendor/evenement/evenement/composer.json | 29 + .../php/vendor/evenement/evenement/doc/00-intro.md | 28 + .../php/vendor/evenement/evenement/doc/01-api.md | 91 + .../evenement/evenement/doc/02-plugin-system.md | 155 ++ .../examples/benchmark-emit-no-arguments.php | 28 + .../evenement/examples/benchmark-emit-once.php | 30 + .../examples/benchmark-emit-one-argument.php | 28 + .../evenement/examples/benchmark-emit.php | 28 + .../examples/benchmark-remove-listener-once.php | 39 + .../vendor/evenement/evenement/phpunit.xml.dist | 24 + .../evenement/src/Evenement/EventEmitter.php | 17 + .../src/Evenement/EventEmitterInterface.php | 22 + .../evenement/src/Evenement/EventEmitterTrait.php | 135 ++ .../tests/Evenement/Tests/EventEmitterTest.php | 438 ++++ .../evenement/tests/Evenement/Tests/Listener.php | 51 + .../evenement/tests/Evenement/Tests/functions.php | 17 + assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md | 110 + assets/php/vendor/guzzlehttp/psr7/LICENSE | 19 + assets/php/vendor/guzzlehttp/psr7/README.md | 739 +++++++ assets/php/vendor/guzzlehttp/psr7/composer.json | 39 + .../vendor/guzzlehttp/psr7/src/AppendStream.php | 233 ++ .../vendor/guzzlehttp/psr7/src/BufferStream.php | 137 ++ .../vendor/guzzlehttp/psr7/src/CachingStream.php | 138 ++ .../vendor/guzzlehttp/psr7/src/DroppingStream.php | 42 + assets/php/vendor/guzzlehttp/psr7/src/FnStream.php | 149 ++ .../vendor/guzzlehttp/psr7/src/InflateStream.php | 52 + .../vendor/guzzlehttp/psr7/src/LazyOpenStream.php | 39 + .../php/vendor/guzzlehttp/psr7/src/LimitStream.php | 155 ++ .../vendor/guzzlehttp/psr7/src/MessageTrait.php | 183 ++ .../vendor/guzzlehttp/psr7/src/MultipartStream.php | 153 ++ .../vendor/guzzlehttp/psr7/src/NoSeekStream.php | 22 + .../php/vendor/guzzlehttp/psr7/src/PumpStream.php | 165 ++ assets/php/vendor/guzzlehttp/psr7/src/Request.php | 142 ++ assets/php/vendor/guzzlehttp/psr7/src/Response.php | 132 ++ .../vendor/guzzlehttp/psr7/src/ServerRequest.php | 358 +++ assets/php/vendor/guzzlehttp/psr7/src/Stream.php | 257 +++ .../guzzlehttp/psr7/src/StreamDecoratorTrait.php | 149 ++ .../vendor/guzzlehttp/psr7/src/StreamWrapper.php | 121 + .../vendor/guzzlehttp/psr7/src/UploadedFile.php | 316 +++ assets/php/vendor/guzzlehttp/psr7/src/Uri.php | 702 ++++++ .../vendor/guzzlehttp/psr7/src/UriNormalizer.php | 216 ++ .../php/vendor/guzzlehttp/psr7/src/UriResolver.php | 219 ++ .../php/vendor/guzzlehttp/psr7/src/functions.php | 828 +++++++ .../guzzlehttp/psr7/src/functions_include.php | 6 + .../nubs/random-name-generator/.coveralls.yml | 3 + .../vendor/nubs/random-name-generator/.gitignore | 2 + .../nubs/random-name-generator/.scrutinizer.yml | 19 + .../vendor/nubs/random-name-generator/.travis.yml | 9 + .../nubs/random-name-generator/CONTRIBUTING.md | 38 + .../nubs/random-name-generator/Dockerfile.tests | 5 + .../php/vendor/nubs/random-name-generator/LICENSE | 21 + .../vendor/nubs/random-name-generator/README.md | 91 + .../vendor/nubs/random-name-generator/build.php | 25 + .../nubs/random-name-generator/composer.json | 30 + .../nubs/random-name-generator/composer.lock | 1963 +++++++++++++++++ .../nubs/random-name-generator/docker-compose.yml | 8 + .../vendor/nubs/random-name-generator/phpunit.xml | 10 + .../src/AbstractGenerator.php | 19 + .../vendor/nubs/random-name-generator/src/All.php | 62 + .../random-name-generator/src/Alliteration.php | 59 + .../nubs/random-name-generator/src/Generator.php | 16 + .../vendor/nubs/random-name-generator/src/Vgng.php | 138 ++ .../nubs/random-name-generator/src/adjectives.txt | 233 ++ .../nubs/random-name-generator/src/nouns.txt | 313 +++ .../random-name-generator/src/video_game_names.txt | 1276 +++++++++++ .../nubs/random-name-generator/tests/AllTest.php | 72 + .../tests/AlliterationTest.php | 66 + .../nubs/random-name-generator/tests/VgngTest.php | 67 + assets/php/vendor/paragonie/random_compat/LICENSE | 22 + .../vendor/paragonie/random_compat/build-phar.sh | 5 + .../vendor/paragonie/random_compat/composer.json | 37 + .../random_compat/dist/random_compat.phar.pubkey | 5 + .../dist/random_compat.phar.pubkey.asc | 11 + .../random_compat/lib/byte_safe_strings.php | 181 ++ .../paragonie/random_compat/lib/cast_to_int.php | 75 + .../paragonie/random_compat/lib/error_polyfill.php | 49 + .../vendor/paragonie/random_compat/lib/random.php | 225 ++ .../random_compat/lib/random_bytes_com_dotnet.php | 88 + .../random_compat/lib/random_bytes_dev_urandom.php | 167 ++ .../random_compat/lib/random_bytes_libsodium.php | 88 + .../lib/random_bytes_libsodium_legacy.php | 92 + .../random_compat/lib/random_bytes_mcrypt.php | 77 + .../paragonie/random_compat/lib/random_int.php | 190 ++ .../paragonie/random_compat/other/build_phar.php | 57 + .../paragonie/random_compat/psalm-autoload.php | 9 + .../php/vendor/paragonie/random_compat/psalm.xml | 18 + assets/php/vendor/psr/http-message/CHANGELOG.md | 36 + assets/php/vendor/psr/http-message/LICENSE | 19 + assets/php/vendor/psr/http-message/README.md | 13 + assets/php/vendor/psr/http-message/composer.json | 26 + .../psr/http-message/src/MessageInterface.php | 187 ++ .../psr/http-message/src/RequestInterface.php | 129 ++ .../psr/http-message/src/ResponseInterface.php | 68 + .../http-message/src/ServerRequestInterface.php | 261 +++ .../psr/http-message/src/StreamInterface.php | 158 ++ .../psr/http-message/src/UploadedFileInterface.php | 123 ++ .../vendor/psr/http-message/src/UriInterface.php | 323 +++ assets/php/vendor/ratchet/rfc6455/.gitignore | 4 + assets/php/vendor/ratchet/rfc6455/.travis.yml | 20 + assets/php/vendor/ratchet/rfc6455/LICENSE | 19 + assets/php/vendor/ratchet/rfc6455/README.md | 13 + assets/php/vendor/ratchet/rfc6455/composer.json | 32 + assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist | 27 + .../rfc6455/src/Handshake/ClientNegotiator.php | 53 + .../rfc6455/src/Handshake/NegotiatorInterface.php | 47 + .../rfc6455/src/Handshake/RequestVerifier.php | 140 ++ .../rfc6455/src/Handshake/ResponseVerifier.php | 52 + .../rfc6455/src/Handshake/ServerNegotiator.php | 136 ++ .../rfc6455/src/Messaging/CloseFrameChecker.php | 24 + .../rfc6455/src/Messaging/DataInterface.php | 34 + .../vendor/ratchet/rfc6455/src/Messaging/Frame.php | 473 ++++ .../rfc6455/src/Messaging/FrameInterface.php | 38 + .../ratchet/rfc6455/src/Messaging/Message.php | 123 ++ .../rfc6455/src/Messaging/MessageBuffer.php | 231 ++ .../rfc6455/src/Messaging/MessageInterface.php | 20 + .../vendor/ratchet/rfc6455/tests/AbResultsTest.php | 30 + .../ratchet/rfc6455/tests/ab/clientRunner.php | 228 ++ .../ratchet/rfc6455/tests/ab/fuzzingclient.json | 14 + .../ratchet/rfc6455/tests/ab/fuzzingserver.json | 10 + .../ratchet/rfc6455/tests/ab/run_ab_tests.sh | 11 + .../ratchet/rfc6455/tests/ab/startServer.php | 55 + .../php/vendor/ratchet/rfc6455/tests/bootstrap.php | 19 + .../tests/unit/Handshake/RequestVerifierTest.php | 177 ++ .../tests/unit/Handshake/ResponseVerifierTest.php | 34 + .../tests/unit/Handshake/ServerNegotiatorTest.php | 175 ++ .../rfc6455/tests/unit/Messaging/FrameTest.php | 501 +++++ .../tests/unit/Messaging/MessageBufferTest.php | 39 + .../rfc6455/tests/unit/Messaging/MessageTest.php | 58 + assets/php/vendor/react/cache/.gitignore | 2 + assets/php/vendor/react/cache/.travis.yml | 25 + assets/php/vendor/react/cache/CHANGELOG.md | 35 + assets/php/vendor/react/cache/LICENSE | 19 + assets/php/vendor/react/cache/README.md | 171 ++ assets/php/vendor/react/cache/composer.json | 19 + assets/php/vendor/react/cache/phpunit.xml.dist | 20 + assets/php/vendor/react/cache/src/ArrayCache.php | 29 + .../php/vendor/react/cache/src/CacheInterface.php | 13 + .../vendor/react/cache/tests/ArrayCacheTest.php | 60 + .../php/vendor/react/cache/tests/CallableStub.php | 10 + assets/php/vendor/react/cache/tests/TestCase.php | 43 + assets/php/vendor/react/dns/.gitignore | 2 + assets/php/vendor/react/dns/.travis.yml | 29 + assets/php/vendor/react/dns/CHANGELOG.md | 179 ++ assets/php/vendor/react/dns/LICENSE | 19 + assets/php/vendor/react/dns/README.md | 209 ++ assets/php/vendor/react/dns/composer.json | 24 + assets/php/vendor/react/dns/examples/01-one.php | 22 + .../vendor/react/dns/examples/02-concurrent.php | 27 + assets/php/vendor/react/dns/examples/03-cached.php | 40 + .../react/dns/examples/04-query-a-and-aaaa.php | 32 + assets/php/vendor/react/dns/phpunit.xml.dist | 25 + .../vendor/react/dns/src/BadServerException.php | 7 + assets/php/vendor/react/dns/src/Config/Config.php | 127 ++ .../react/dns/src/Config/FilesystemFactory.php | 73 + .../php/vendor/react/dns/src/Config/HostsFile.php | 151 ++ .../php/vendor/react/dns/src/Model/HeaderBag.php | 56 + assets/php/vendor/react/dns/src/Model/Message.php | 100 + assets/php/vendor/react/dns/src/Model/Record.php | 21 + .../vendor/react/dns/src/Protocol/BinaryDumper.php | 62 + .../php/vendor/react/dns/src/Protocol/Parser.php | 254 +++ .../vendor/react/dns/src/Query/CachedExecutor.php | 55 + .../react/dns/src/Query/CancellationException.php | 7 + assets/php/vendor/react/dns/src/Query/Executor.php | 156 ++ .../react/dns/src/Query/ExecutorInterface.php | 8 + .../react/dns/src/Query/HostsFileExecutor.php | 89 + assets/php/vendor/react/dns/src/Query/Query.php | 19 + .../php/vendor/react/dns/src/Query/RecordBag.php | 27 + .../php/vendor/react/dns/src/Query/RecordCache.php | 82 + .../vendor/react/dns/src/Query/RetryExecutor.php | 44 + .../react/dns/src/Query/TimeoutException.php | 7 + .../vendor/react/dns/src/Query/TimeoutExecutor.php | 32 + .../react/dns/src/RecordNotFoundException.php | 7 + .../php/vendor/react/dns/src/Resolver/Factory.php | 103 + .../php/vendor/react/dns/src/Resolver/Resolver.php | 100 + assets/php/vendor/react/dns/tests/CallableStub.php | 10 + .../vendor/react/dns/tests/Config/ConfigTest.php | 189 ++ .../dns/tests/Config/FilesystemFactoryTest.php | 70 + .../react/dns/tests/Config/HostsFileTest.php | 170 ++ .../react/dns/tests/Fixtures/etc/resolv.conf | 1 + .../react/dns/tests/FunctionalResolverTest.php | 71 + .../vendor/react/dns/tests/Model/MessageTest.php | 30 + .../react/dns/tests/Protocol/BinaryDumperTest.php | 48 + .../vendor/react/dns/tests/Protocol/ParserTest.php | 343 +++ .../react/dns/tests/Query/CachedExecutorTest.php | 100 + .../vendor/react/dns/tests/Query/ExecutorTest.php | 308 +++ .../dns/tests/Query/HostsFileExecutorTest.php | 126 ++ .../vendor/react/dns/tests/Query/RecordBagTest.php | 64 + .../react/dns/tests/Query/RecordCacheTest.php | 123 ++ .../react/dns/tests/Query/RetryExecutorTest.php | 197 ++ .../react/dns/tests/Query/TimeoutExecutorTest.php | 115 + .../react/dns/tests/Resolver/FactoryTest.php | 131 ++ .../dns/tests/Resolver/ResolveAliasesTest.php | 100 + .../react/dns/tests/Resolver/ResolverTest.php | 129 ++ assets/php/vendor/react/dns/tests/TestCase.php | 61 + assets/php/vendor/react/event-loop/.gitignore | 3 + assets/php/vendor/react/event-loop/.travis.yml | 39 + assets/php/vendor/react/event-loop/CHANGELOG.md | 316 +++ assets/php/vendor/react/event-loop/LICENSE | 19 + assets/php/vendor/react/event-loop/README.md | 702 ++++++ assets/php/vendor/react/event-loop/composer.json | 21 + .../vendor/react/event-loop/examples/01-timers.php | 15 + .../react/event-loop/examples/02-periodic.php | 16 + .../vendor/react/event-loop/examples/03-ticks.php | 15 + .../react/event-loop/examples/04-signals.php | 19 + .../react/event-loop/examples/11-consume-stdin.php | 30 + .../react/event-loop/examples/12-generate-yes.php | 41 + .../examples/13-http-client-blocking.php | 35 + .../event-loop/examples/14-http-client-async.php | 63 + .../react/event-loop/examples/21-http-server.php | 36 + .../event-loop/examples/91-benchmark-ticks.php | 15 + .../event-loop/examples/92-benchmark-timers.php | 15 + .../examples/93-benchmark-ticks-delay.php | 22 + .../examples/94-benchmark-timers-delay.php | 22 + .../event-loop/examples/95-benchmark-memory.php | 67 + .../php/vendor/react/event-loop/phpunit.xml.dist | 25 + .../php/vendor/react/event-loop/src/ExtEvLoop.php | 252 +++ .../vendor/react/event-loop/src/ExtEventLoop.php | 259 +++ .../vendor/react/event-loop/src/ExtLibevLoop.php | 199 ++ .../react/event-loop/src/ExtLibeventLoop.php | 283 +++ assets/php/vendor/react/event-loop/src/Factory.php | 41 + .../vendor/react/event-loop/src/LoopInterface.php | 463 ++++ .../vendor/react/event-loop/src/SignalsHandler.php | 63 + .../react/event-loop/src/StreamSelectLoop.php | 275 +++ .../react/event-loop/src/Tick/FutureTickQueue.php | 60 + .../vendor/react/event-loop/src/Timer/Timer.php | 55 + .../vendor/react/event-loop/src/Timer/Timers.php | 109 + .../vendor/react/event-loop/src/TimerInterface.php | 27 + .../react/event-loop/tests/AbstractLoopTest.php | 621 ++++++ .../vendor/react/event-loop/tests/CallableStub.php | 10 + .../react/event-loop/tests/ExtEvLoopTest.php | 17 + .../react/event-loop/tests/ExtEventLoopTest.php | 84 + .../react/event-loop/tests/ExtLibevLoopTest.php | 22 + .../react/event-loop/tests/ExtLibeventLoopTest.php | 58 + .../react/event-loop/tests/SignalsHandlerTest.php | 55 + .../event-loop/tests/StreamSelectLoopTest.php | 148 ++ .../php/vendor/react/event-loop/tests/TestCase.php | 53 + .../event-loop/tests/Timer/AbstractTimerTest.php | 122 + .../event-loop/tests/Timer/ExtEvTimerTest.php | 17 + .../event-loop/tests/Timer/ExtEventTimerTest.php | 17 + .../event-loop/tests/Timer/ExtLibevTimerTest.php | 17 + .../tests/Timer/ExtLibeventTimerTest.php | 17 + .../tests/Timer/StreamSelectTimerTest.php | 13 + .../react/event-loop/tests/Timer/TimersTest.php | 27 + .../vendor/react/event-loop/tests/bootstrap.php | 15 + assets/php/vendor/react/event-loop/travis-init.sh | 42 + assets/php/vendor/react/promise-timer/.gitignore | 2 + assets/php/vendor/react/promise-timer/.travis.yml | 26 + assets/php/vendor/react/promise-timer/CHANGELOG.md | 40 + assets/php/vendor/react/promise-timer/LICENSE | 21 + assets/php/vendor/react/promise-timer/README.md | 372 ++++ .../php/vendor/react/promise-timer/composer.json | 28 + .../vendor/react/promise-timer/phpunit.xml.dist | 19 + .../react/promise-timer/src/TimeoutException.php | 22 + .../vendor/react/promise-timer/src/functions.php | 70 + .../react/promise-timer/tests/CallableStub.php | 10 + .../promise-timer/tests/FunctionRejectTest.php | 49 + .../promise-timer/tests/FunctionResolveTest.php | 71 + .../promise-timer/tests/FunctionTimeoutTest.php | 169 ++ .../vendor/react/promise-timer/tests/TestCase.php | 61 + .../promise-timer/tests/TimeoutExceptionTest.php | 15 + assets/php/vendor/react/promise/.gitignore | 5 + assets/php/vendor/react/promise/.travis.yml | 22 + assets/php/vendor/react/promise/CHANGELOG.md | 96 + assets/php/vendor/react/promise/LICENSE | 22 + assets/php/vendor/react/promise/README.md | 840 +++++++ assets/php/vendor/react/promise/composer.json | 29 + assets/php/vendor/react/promise/phpunit.xml.dist | 28 + .../promise/src/CancellablePromiseInterface.php | 11 + .../vendor/react/promise/src/CancellationQueue.php | 55 + assets/php/vendor/react/promise/src/Deferred.php | 60 + .../promise/src/Exception/LengthException.php | 7 + .../react/promise/src/ExtendedPromiseInterface.php | 26 + .../vendor/react/promise/src/FulfilledPromise.php | 68 + .../php/vendor/react/promise/src/LazyPromise.php | 63 + assets/php/vendor/react/promise/src/Promise.php | 216 ++ .../vendor/react/promise/src/PromiseInterface.php | 11 + .../vendor/react/promise/src/PromisorInterface.php | 11 + .../vendor/react/promise/src/RejectedPromise.php | 76 + .../promise/src/UnhandledRejectionException.php | 31 + assets/php/vendor/react/promise/src/functions.php | 244 ++ .../vendor/react/promise/src/functions_include.php | 5 + .../react/promise/tests/CancellationQueueTest.php | 100 + .../vendor/react/promise/tests/DeferredTest.php | 42 + .../react/promise/tests/FulfilledPromiseTest.php | 50 + .../vendor/react/promise/tests/FunctionAllTest.php | 114 + .../vendor/react/promise/tests/FunctionAnyTest.php | 204 ++ .../promise/tests/FunctionCheckTypehintTest.php | 118 + .../vendor/react/promise/tests/FunctionMapTest.php | 198 ++ .../react/promise/tests/FunctionRaceTest.php | 211 ++ .../react/promise/tests/FunctionReduceTest.php | 347 +++ .../react/promise/tests/FunctionRejectTest.php | 64 + .../react/promise/tests/FunctionResolveTest.php | 171 ++ .../react/promise/tests/FunctionSomeTest.php | 258 +++ .../vendor/react/promise/tests/LazyPromiseTest.php | 107 + .../PromiseAdapter/CallbackPromiseAdapter.php | 40 + .../PromiseAdapter/PromiseAdapterInterface.php | 14 + .../php/vendor/react/promise/tests/PromiseTest.php | 84 + .../promise/tests/PromiseTest/CancelTestTrait.php | 231 ++ .../promise/tests/PromiseTest/FullTestTrait.php | 15 + .../promise/tests/PromiseTest/NotifyTestTrait.php | 336 +++ .../PromiseTest/PromiseFulfilledTestTrait.php | 351 +++ .../tests/PromiseTest/PromisePendingTestTrait.php | 68 + .../tests/PromiseTest/PromiseRejectedTestTrait.php | 512 +++++ .../tests/PromiseTest/PromiseSettledTestTrait.php | 86 + .../promise/tests/PromiseTest/RejectTestTrait.php | 368 ++++ .../promise/tests/PromiseTest/ResolveTestTrait.php | 312 +++ .../react/promise/tests/RejectedPromiseTest.php | 50 + .../react/promise/tests/Stub/CallableStub.php | 10 + assets/php/vendor/react/promise/tests/TestCase.php | 43 + .../php/vendor/react/promise/tests/bootstrap.php | 7 + .../tests/fixtures/SimpleFulfilledTestPromise.php | 21 + .../tests/fixtures/SimpleFulfilledTestThenable.php | 21 + .../tests/fixtures/SimpleRejectedTestPromise.php | 21 + .../tests/fixtures/SimpleTestCancellable.php | 13 + .../fixtures/SimpleTestCancellableThenable.php | 18 + assets/php/vendor/react/socket/.gitignore | 2 + assets/php/vendor/react/socket/.travis.yml | 49 + assets/php/vendor/react/socket/CHANGELOG.md | 451 ++++ assets/php/vendor/react/socket/LICENSE | 19 + assets/php/vendor/react/socket/README.md | 1419 ++++++++++++ assets/php/vendor/react/socket/composer.json | 29 + .../react/socket/examples/01-echo-server.php | 42 + .../react/socket/examples/02-chat-server.php | 59 + .../react/socket/examples/03-http-server.php | 57 + .../react/socket/examples/11-http-client.php | 36 + .../react/socket/examples/12-https-client.php | 36 + .../react/socket/examples/21-netcat-client.php | 68 + .../react/socket/examples/22-http-client.php | 60 + .../react/socket/examples/91-benchmark-server.php | 60 + .../socket/examples/99-generate-self-signed.php | 31 + .../php/vendor/react/socket/examples/localhost.pem | 49 + .../react/socket/examples/localhost_swordfish.pem | 51 + assets/php/vendor/react/socket/phpunit.xml.dist | 25 + assets/php/vendor/react/socket/src/Connection.php | 178 ++ .../react/socket/src/ConnectionInterface.php | 119 + assets/php/vendor/react/socket/src/Connector.php | 136 ++ .../vendor/react/socket/src/ConnectorInterface.php | 58 + .../php/vendor/react/socket/src/DnsConnector.php | 111 + .../vendor/react/socket/src/FixedUriConnector.php | 41 + .../php/vendor/react/socket/src/LimitingServer.php | 203 ++ .../vendor/react/socket/src/SecureConnector.php | 64 + .../php/vendor/react/socket/src/SecureServer.php | 192 ++ assets/php/vendor/react/socket/src/Server.php | 73 + .../vendor/react/socket/src/ServerInterface.php | 151 ++ .../vendor/react/socket/src/StreamEncryption.php | 146 ++ .../php/vendor/react/socket/src/TcpConnector.php | 122 + assets/php/vendor/react/socket/src/TcpServer.php | 236 ++ .../vendor/react/socket/src/TimeoutConnector.php | 25 + .../php/vendor/react/socket/src/UnixConnector.php | 44 + assets/php/vendor/react/socket/src/UnixServer.php | 130 ++ .../vendor/react/socket/tests/ConnectionTest.php | 47 + .../vendor/react/socket/tests/ConnectorTest.php | 128 ++ .../vendor/react/socket/tests/DnsConnectorTest.php | 111 + .../react/socket/tests/FixedUriConnectorTest.php | 19 + .../react/socket/tests/FunctionalConnectorTest.php | 32 + .../socket/tests/FunctionalSecureServerTest.php | 438 ++++ .../react/socket/tests/FunctionalTcpServerTest.php | 324 +++ .../vendor/react/socket/tests/IntegrationTest.php | 171 ++ .../react/socket/tests/LimitingServerTest.php | 195 ++ .../react/socket/tests/SecureConnectorTest.php | 74 + .../react/socket/tests/SecureIntegrationTest.php | 204 ++ .../vendor/react/socket/tests/SecureServerTest.php | 105 + .../php/vendor/react/socket/tests/ServerTest.php | 173 ++ .../react/socket/tests/Stub/CallableStub.php | 10 + .../react/socket/tests/Stub/ConnectionStub.php | 63 + .../vendor/react/socket/tests/Stub/ServerStub.php | 18 + .../vendor/react/socket/tests/TcpConnectorTest.php | 255 +++ .../vendor/react/socket/tests/TcpServerTest.php | 285 +++ assets/php/vendor/react/socket/tests/TestCase.php | 101 + .../react/socket/tests/TimeoutConnectorTest.php | 103 + .../react/socket/tests/UnixConnectorTest.php | 64 + .../vendor/react/socket/tests/UnixServerTest.php | 283 +++ assets/php/vendor/react/stream/.gitignore | 2 + assets/php/vendor/react/stream/.travis.yml | 50 + assets/php/vendor/react/stream/CHANGELOG.md | 377 ++++ assets/php/vendor/react/stream/LICENSE | 19 + assets/php/vendor/react/stream/README.md | 1224 ++++++++++ assets/php/vendor/react/stream/composer.json | 25 + .../php/vendor/react/stream/examples/01-http.php | 40 + .../php/vendor/react/stream/examples/02-https.php | 40 + assets/php/vendor/react/stream/examples/11-cat.php | 28 + .../stream/examples/91-benchmark-throughput.php | 62 + assets/php/vendor/react/stream/phpunit.xml.dist | 25 + .../vendor/react/stream/src/CompositeStream.php | 82 + .../react/stream/src/DuplexResourceStream.php | 224 ++ .../react/stream/src/DuplexStreamInterface.php | 39 + .../react/stream/src/ReadableResourceStream.php | 177 ++ .../react/stream/src/ReadableStreamInterface.php | 362 +++ .../php/vendor/react/stream/src/ThroughStream.php | 190 ++ assets/php/vendor/react/stream/src/Util.php | 75 + .../react/stream/src/WritableResourceStream.php | 171 ++ .../react/stream/src/WritableStreamInterface.php | 347 +++ .../php/vendor/react/stream/tests/CallableStub.php | 10 + .../react/stream/tests/CompositeStreamTest.php | 267 +++ .../tests/DuplexResourceStreamIntegrationTest.php | 352 +++ .../stream/tests/DuplexResourceStreamTest.php | 495 +++++ .../react/stream/tests/EnforceBlockingWrapper.php | 35 + .../react/stream/tests/FunctionalInternetTest.php | 122 + .../stream/tests/ReadableResourceStreamTest.php | 372 ++++ .../react/stream/tests/Stub/ReadableStreamStub.php | 61 + assets/php/vendor/react/stream/tests/TestCase.php | 54 + .../react/stream/tests/ThroughStreamTest.php | 267 +++ assets/php/vendor/react/stream/tests/UtilTest.php | 273 +++ .../stream/tests/WritableStreamResourceTest.php | 534 +++++ .../php/vendor/symfony/http-foundation/.gitignore | 3 + .../symfony/http-foundation/AcceptHeader.php | 168 ++ .../symfony/http-foundation/AcceptHeaderItem.php | 209 ++ .../symfony/http-foundation/ApacheRequest.php | 43 + .../symfony/http-foundation/BinaryFileResponse.php | 359 +++ .../vendor/symfony/http-foundation/CHANGELOG.md | 159 ++ .../php/vendor/symfony/http-foundation/Cookie.php | 289 +++ .../Exception/ConflictingHeadersException.php | 21 + .../Exception/RequestExceptionInterface.php | 21 + .../Exception/SuspiciousOperationException.php | 20 + .../http-foundation/ExpressionRequestMatcher.php | 47 + .../File/Exception/AccessDeniedException.php | 28 + .../File/Exception/FileException.php | 21 + .../File/Exception/FileNotFoundException.php | 28 + .../File/Exception/UnexpectedTypeException.php | 20 + .../File/Exception/UploadException.php | 21 + .../vendor/symfony/http-foundation/File/File.php | 136 ++ .../File/MimeType/ExtensionGuesser.php | 94 + .../File/MimeType/ExtensionGuesserInterface.php | 27 + .../File/MimeType/FileBinaryMimeTypeGuesser.php | 85 + .../File/MimeType/FileinfoMimeTypeGuesser.php | 69 + .../File/MimeType/MimeTypeExtensionGuesser.php | 808 +++++++ .../File/MimeType/MimeTypeGuesser.php | 142 ++ .../File/MimeType/MimeTypeGuesserInterface.php | 35 + .../vendor/symfony/http-foundation/File/Stream.php | 28 + .../symfony/http-foundation/File/UploadedFile.php | 266 +++ .../php/vendor/symfony/http-foundation/FileBag.php | 144 ++ .../vendor/symfony/http-foundation/HeaderBag.php | 331 +++ .../php/vendor/symfony/http-foundation/IpUtils.php | 156 ++ .../symfony/http-foundation/JsonResponse.php | 220 ++ assets/php/vendor/symfony/http-foundation/LICENSE | 19 + .../symfony/http-foundation/ParameterBag.php | 234 ++ .../php/vendor/symfony/http-foundation/README.md | 14 + .../symfony/http-foundation/RedirectResponse.php | 109 + .../php/vendor/symfony/http-foundation/Request.php | 2154 ++++++++++++++++++ .../symfony/http-foundation/RequestMatcher.php | 178 ++ .../http-foundation/RequestMatcherInterface.php | 27 + .../symfony/http-foundation/RequestStack.php | 103 + .../vendor/symfony/http-foundation/Response.php | 1298 +++++++++++ .../symfony/http-foundation/ResponseHeaderBag.php | 340 +++ .../vendor/symfony/http-foundation/ServerBag.php | 102 + .../Session/Attribute/AttributeBag.php | 148 ++ .../Session/Attribute/AttributeBagInterface.php | 72 + .../Session/Attribute/NamespacedAttributeBag.php | 153 ++ .../Session/Flash/AutoExpireFlashBag.php | 161 ++ .../http-foundation/Session/Flash/FlashBag.php | 152 ++ .../Session/Flash/FlashBagInterface.php | 93 + .../symfony/http-foundation/Session/Session.php | 273 +++ .../Session/SessionBagInterface.php | 46 + .../http-foundation/Session/SessionBagProxy.php | 82 + .../http-foundation/Session/SessionInterface.php | 180 ++ .../Storage/Handler/AbstractSessionHandler.php | 168 ++ .../Storage/Handler/MemcacheSessionHandler.php | 120 + .../Storage/Handler/MemcachedSessionHandler.php | 124 ++ .../Storage/Handler/MongoDbSessionHandler.php | 255 +++ .../Storage/Handler/NativeFileSessionHandler.php | 55 + .../Storage/Handler/NativeSessionHandler.php | 24 + .../Session/Storage/Handler/NullSessionHandler.php | 76 + .../Session/Storage/Handler/PdoSessionHandler.php | 910 ++++++++ .../Storage/Handler/StrictSessionHandler.php | 103 + .../Storage/Handler/WriteCheckSessionHandler.php | 92 + .../Session/Storage/MetadataBag.php | 168 ++ .../Session/Storage/MockArraySessionStorage.php | 256 +++ .../Session/Storage/MockFileSessionStorage.php | 152 ++ .../Session/Storage/NativeSessionStorage.php | 445 ++++ .../Session/Storage/PhpBridgeSessionStorage.php | 59 + .../Session/Storage/Proxy/AbstractProxy.php | 122 + .../Session/Storage/Proxy/NativeProxy.php | 40 + .../Session/Storage/Proxy/SessionHandlerProxy.php | 85 + .../Session/Storage/SessionStorageInterface.php | 137 ++ .../symfony/http-foundation/StreamedResponse.php | 144 ++ .../http-foundation/Tests/AcceptHeaderItemTest.php | 113 + .../http-foundation/Tests/AcceptHeaderTest.php | 103 + .../http-foundation/Tests/ApacheRequestTest.php | 93 + .../Tests/BinaryFileResponseTest.php | 352 +++ .../symfony/http-foundation/Tests/CookieTest.php | 223 ++ .../Tests/ExpressionRequestMatcherTest.php | 69 + .../http-foundation/Tests/File/FakeFile.php | 45 + .../http-foundation/Tests/File/FileTest.php | 180 ++ .../Tests/File/Fixtures/.unknownextension | 1 + .../Tests/File/Fixtures/directory/.empty | 0 .../Tests/File/Fixtures/other-file.example | 0 .../http-foundation/Tests/File/Fixtures/test | Bin 0 -> 35 bytes .../http-foundation/Tests/File/Fixtures/test.gif | Bin 0 -> 35 bytes .../Tests/File/MimeType/MimeTypeTest.php | 90 + .../Tests/File/UploadedFileTest.php | 273 +++ .../symfony/http-foundation/Tests/FileBagTest.php | 175 ++ .../http-foundation/Tests/HeaderBagTest.php | 205 ++ .../symfony/http-foundation/Tests/IpUtilsTest.php | 104 + .../http-foundation/Tests/JsonResponseTest.php | 257 +++ .../http-foundation/Tests/ParameterBagTest.php | 194 ++ .../http-foundation/Tests/RedirectResponseTest.php | 97 + .../http-foundation/Tests/RequestMatcherTest.php | 151 ++ .../http-foundation/Tests/RequestStackTest.php | 70 + .../symfony/http-foundation/Tests/RequestTest.php | 2329 ++++++++++++++++++++ .../Tests/ResponseHeaderBagTest.php | 363 +++ .../symfony/http-foundation/Tests/ResponseTest.php | 1003 +++++++++ .../http-foundation/Tests/ResponseTestCase.php | 89 + .../http-foundation/Tests/ServerBagTest.php | 170 ++ .../Tests/Session/Attribute/AttributeBagTest.php | 186 ++ .../Attribute/NamespacedAttributeBagTest.php | 182 ++ .../Tests/Session/Flash/AutoExpireFlashBagTest.php | 161 ++ .../Tests/Session/Flash/FlashBagTest.php | 132 ++ .../http-foundation/Tests/Session/SessionTest.php | 242 ++ .../Storage/Handler/AbstractSessionHandlerTest.php | 61 + .../Session/Storage/Handler/Fixtures/common.inc | 151 ++ .../Handler/Fixtures/empty_destroys.expected | 17 + .../Storage/Handler/Fixtures/empty_destroys.php | 8 + .../Storage/Handler/Fixtures/read_only.expected | 14 + .../Session/Storage/Handler/Fixtures/read_only.php | 8 + .../Storage/Handler/Fixtures/regenerate.expected | 24 + .../Storage/Handler/Fixtures/regenerate.php | 10 + .../Storage/Handler/Fixtures/storage.expected | 20 + .../Session/Storage/Handler/Fixtures/storage.php | 24 + .../Storage/Handler/Fixtures/with_cookie.expected | 15 + .../Storage/Handler/Fixtures/with_cookie.php | 8 + .../Fixtures/with_cookie_and_session.expected | 24 + .../Handler/Fixtures/with_cookie_and_session.php | 13 + .../Storage/Handler/MemcacheSessionHandlerTest.php | 135 ++ .../Handler/MemcachedSessionHandlerTest.php | 139 ++ .../Storage/Handler/MongoDbSessionHandlerTest.php | 333 +++ .../Handler/NativeFileSessionHandlerTest.php | 77 + .../Storage/Handler/NativeSessionHandlerTest.php | 38 + .../Storage/Handler/NullSessionHandlerTest.php | 59 + .../Storage/Handler/PdoSessionHandlerTest.php | 411 ++++ .../Storage/Handler/StrictSessionHandlerTest.php | 189 ++ .../Handler/WriteCheckSessionHandlerTest.php | 97 + .../Tests/Session/Storage/MetadataBagTest.php | 139 ++ .../Storage/MockArraySessionStorageTest.php | 131 ++ .../Session/Storage/MockFileSessionStorageTest.php | 127 ++ .../Session/Storage/NativeSessionStorageTest.php | 277 +++ .../Storage/PhpBridgeSessionStorageTest.php | 96 + .../Session/Storage/Proxy/AbstractProxyTest.php | 113 + .../Session/Storage/Proxy/NativeProxyTest.php | 38 + .../Storage/Proxy/SessionHandlerProxyTest.php | 124 ++ .../http-foundation/Tests/StreamedResponseTest.php | 126 ++ .../Tests/schema/http-status-codes.rng | 31 + .../http-foundation/Tests/schema/iana-registry.rng | 198 ++ .../vendor/symfony/http-foundation/composer.json | 38 + .../symfony/http-foundation/phpunit.xml.dist | 31 + .../php/vendor/symfony/polyfill-mbstring/LICENSE | 19 + .../vendor/symfony/polyfill-mbstring/Mbstring.php | 791 +++++++ .../php/vendor/symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/lowerCase.php | 1101 +++++++++ .../Resources/unidata/upperCase.php | 1109 ++++++++++ .../vendor/symfony/polyfill-mbstring/bootstrap.php | 58 + .../vendor/symfony/polyfill-mbstring/composer.json | 34 + assets/php/vendor/symfony/polyfill-php70/LICENSE | 19 + assets/php/vendor/symfony/polyfill-php70/Php70.php | 74 + assets/php/vendor/symfony/polyfill-php70/README.md | 28 + .../Resources/stubs/ArithmeticError.php | 5 + .../Resources/stubs/AssertionError.php | 5 + .../Resources/stubs/DivisionByZeroError.php | 5 + .../polyfill-php70/Resources/stubs/Error.php | 5 + .../polyfill-php70/Resources/stubs/ParseError.php | 5 + .../SessionUpdateTimestampHandlerInterface.php | 23 + .../polyfill-php70/Resources/stubs/TypeError.php | 5 + .../vendor/symfony/polyfill-php70/bootstrap.php | 27 + .../vendor/symfony/polyfill-php70/composer.json | 33 + assets/php/vendor/symfony/routing/.gitignore | 3 + .../vendor/symfony/routing/Annotation/Route.php | 144 ++ assets/php/vendor/symfony/routing/CHANGELOG.md | 228 ++ .../php/vendor/symfony/routing/CompiledRoute.php | 169 ++ .../DependencyInjection/RoutingResolverPass.php | 49 + .../routing/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidParameterException.php | 21 + .../Exception/MethodNotAllowedException.php | 41 + .../MissingMandatoryParametersException.php | 22 + .../routing/Exception/NoConfigurationException.php | 21 + .../Exception/ResourceNotFoundException.php | 23 + .../routing/Exception/RouteNotFoundException.php | 21 + .../ConfigurableRequirementsInterface.php | 55 + .../routing/Generator/Dumper/GeneratorDumper.php | 37 + .../Generator/Dumper/GeneratorDumperInterface.php | 39 + .../Generator/Dumper/PhpGeneratorDumper.php | 118 + .../symfony/routing/Generator/UrlGenerator.php | 321 +++ .../routing/Generator/UrlGeneratorInterface.php | 86 + assets/php/vendor/symfony/routing/LICENSE | 19 + .../routing/Loader/AnnotationClassLoader.php | 269 +++ .../routing/Loader/AnnotationDirectoryLoader.php | 93 + .../routing/Loader/AnnotationFileLoader.php | 142 ++ .../symfony/routing/Loader/ClosureLoader.php | 46 + .../Loader/Configurator/CollectionConfigurator.php | 81 + .../Loader/Configurator/ImportConfigurator.php | 49 + .../Loader/Configurator/RouteConfigurator.php | 34 + .../Loader/Configurator/RoutingConfigurator.php | 62 + .../Loader/Configurator/Traits/AddTrait.php | 55 + .../Loader/Configurator/Traits/RouteTrait.php | 131 ++ .../DependencyInjection/ServiceRouterLoader.php | 40 + .../symfony/routing/Loader/DirectoryLoader.php | 58 + .../symfony/routing/Loader/GlobFileLoader.php | 47 + .../symfony/routing/Loader/ObjectRouteLoader.php | 95 + .../symfony/routing/Loader/PhpFileLoader.php | 75 + .../symfony/routing/Loader/XmlFileLoader.php | 359 +++ .../symfony/routing/Loader/YamlFileLoader.php | 233 ++ .../routing/Loader/schema/routing/routing-1.0.xsd | 148 ++ .../routing/Matcher/Dumper/DumperCollection.php | 159 ++ .../symfony/routing/Matcher/Dumper/DumperRoute.php | 57 + .../routing/Matcher/Dumper/MatcherDumper.php | 37 + .../Matcher/Dumper/MatcherDumperInterface.php | 39 + .../routing/Matcher/Dumper/PhpMatcherDumper.php | 429 ++++ .../Matcher/Dumper/StaticPrefixCollection.php | 238 ++ .../routing/Matcher/RedirectableUrlMatcher.php | 65 + .../Matcher/RedirectableUrlMatcherInterface.php | 31 + .../routing/Matcher/RequestMatcherInterface.php | 39 + .../routing/Matcher/TraceableUrlMatcher.php | 141 ++ .../vendor/symfony/routing/Matcher/UrlMatcher.php | 252 +++ .../routing/Matcher/UrlMatcherInterface.php | 41 + assets/php/vendor/symfony/routing/README.md | 13 + .../php/vendor/symfony/routing/RequestContext.php | 336 +++ .../routing/RequestContextAwareInterface.php | 27 + assets/php/vendor/symfony/routing/Route.php | 558 +++++ .../php/vendor/symfony/routing/RouteCollection.php | 280 +++ .../symfony/routing/RouteCollectionBuilder.php | 380 ++++ .../php/vendor/symfony/routing/RouteCompiler.php | 316 +++ .../symfony/routing/RouteCompilerInterface.php | 30 + assets/php/vendor/symfony/routing/Router.php | 388 ++++ .../php/vendor/symfony/routing/RouterInterface.php | 32 + .../symfony/routing/Tests/Annotation/RouteTest.php | 50 + .../symfony/routing/Tests/CompiledRouteTest.php | 27 + .../RoutingResolverPassTest.php | 36 + .../Fixtures/AnnotatedClasses/AbstractClass.php | 16 + .../Tests/Fixtures/AnnotatedClasses/BarClass.php | 19 + .../Tests/Fixtures/AnnotatedClasses/BazClass.php | 19 + .../Tests/Fixtures/AnnotatedClasses/FooClass.php | 16 + .../Tests/Fixtures/AnnotatedClasses/FooTrait.php | 13 + .../routing/Tests/Fixtures/CustomCompiledRoute.php | 18 + .../routing/Tests/Fixtures/CustomRouteCompiler.php | 26 + .../routing/Tests/Fixtures/CustomXmlFileLoader.php | 26 + .../AnonymousClassInTrait.php | 24 + .../OtherAnnotatedClasses/NoStartTagClass.php | 3 + .../OtherAnnotatedClasses/VariadicClass.php | 19 + .../Tests/Fixtures/RedirectableUrlMatcher.php | 30 + .../symfony/routing/Tests/Fixtures/annotated.php | 0 .../symfony/routing/Tests/Fixtures/bad_format.yml | 3 + .../vendor/symfony/routing/Tests/Fixtures/bar.xml | 0 .../Fixtures/controller/import__controller.xml | 10 + .../Fixtures/controller/import__controller.yml | 4 + .../Fixtures/controller/import_controller.xml | 8 + .../Fixtures/controller/import_controller.yml | 3 + .../controller/import_override_defaults.xml | 10 + .../controller/import_override_defaults.yml | 5 + .../Fixtures/controller/override_defaults.xml | 10 + .../Fixtures/controller/override_defaults.yml | 5 + .../routing/Tests/Fixtures/controller/routing.xml | 14 + .../routing/Tests/Fixtures/controller/routing.yml | 11 + .../Tests/Fixtures/directory/recurse/routes1.yml | 2 + .../Tests/Fixtures/directory/recurse/routes2.yml | 2 + .../routing/Tests/Fixtures/directory/routes3.yml | 2 + .../Tests/Fixtures/directory_import/import.yml | 3 + .../routing/Tests/Fixtures/dumper/url_matcher0.php | 37 + .../routing/Tests/Fixtures/dumper/url_matcher1.php | 318 +++ .../routing/Tests/Fixtures/dumper/url_matcher2.php | 380 ++++ .../routing/Tests/Fixtures/dumper/url_matcher3.php | 55 + .../routing/Tests/Fixtures/dumper/url_matcher4.php | 112 + .../routing/Tests/Fixtures/dumper/url_matcher5.php | 209 ++ .../routing/Tests/Fixtures/dumper/url_matcher6.php | 213 ++ .../routing/Tests/Fixtures/dumper/url_matcher7.php | 249 +++ .../symfony/routing/Tests/Fixtures/empty.yml | 0 .../routing/Tests/Fixtures/file_resource.yml | 0 .../vendor/symfony/routing/Tests/Fixtures/foo.xml | 0 .../vendor/symfony/routing/Tests/Fixtures/foo1.xml | 0 .../symfony/routing/Tests/Fixtures/glob/bar.xml | 8 + .../symfony/routing/Tests/Fixtures/glob/bar.yml | 4 + .../symfony/routing/Tests/Fixtures/glob/baz.xml | 8 + .../symfony/routing/Tests/Fixtures/glob/baz.yml | 4 + .../Tests/Fixtures/glob/import_multiple.xml | 8 + .../Tests/Fixtures/glob/import_multiple.yml | 2 + .../routing/Tests/Fixtures/glob/import_single.xml | 8 + .../routing/Tests/Fixtures/glob/import_single.yml | 2 + .../routing/Tests/Fixtures/glob/php_dsl.php | 7 + .../routing/Tests/Fixtures/glob/php_dsl_bar.php | 12 + .../routing/Tests/Fixtures/glob/php_dsl_baz.php | 12 + .../symfony/routing/Tests/Fixtures/incomplete.yml | 2 + .../routing/Tests/Fixtures/list_defaults.xml | 20 + .../Tests/Fixtures/list_in_list_defaults.xml | 22 + .../Tests/Fixtures/list_in_map_defaults.xml | 22 + .../routing/Tests/Fixtures/list_null_values.xml | 22 + .../routing/Tests/Fixtures/map_defaults.xml | 20 + .../Tests/Fixtures/map_in_list_defaults.xml | 22 + .../routing/Tests/Fixtures/map_in_map_defaults.xml | 22 + .../routing/Tests/Fixtures/map_null_values.xml | 22 + .../symfony/routing/Tests/Fixtures/missing_id.xml | 8 + .../routing/Tests/Fixtures/missing_path.xml | 8 + .../routing/Tests/Fixtures/namespaceprefix.xml | 16 + .../Fixtures/nonesense_resource_plus_path.yml | 3 + .../Fixtures/nonesense_type_without_resource.yml | 3 + .../symfony/routing/Tests/Fixtures/nonvalid.xml | 10 + .../symfony/routing/Tests/Fixtures/nonvalid.yml | 1 + .../symfony/routing/Tests/Fixtures/nonvalid2.yml | 1 + .../routing/Tests/Fixtures/nonvalidkeys.yml | 3 + .../routing/Tests/Fixtures/nonvalidnode.xml | 8 + .../routing/Tests/Fixtures/nonvalidroute.xml | 12 + .../symfony/routing/Tests/Fixtures/null_values.xml | 12 + .../symfony/routing/Tests/Fixtures/php_dsl.php | 22 + .../symfony/routing/Tests/Fixtures/php_dsl_sub.php | 14 + .../routing/Tests/Fixtures/scalar_defaults.xml | 33 + .../routing/Tests/Fixtures/special_route_name.yml | 2 + .../routing/Tests/Fixtures/validpattern.php | 18 + .../routing/Tests/Fixtures/validpattern.xml | 15 + .../routing/Tests/Fixtures/validpattern.yml | 13 + .../routing/Tests/Fixtures/validresource.php | 18 + .../routing/Tests/Fixtures/validresource.xml | 13 + .../routing/Tests/Fixtures/validresource.yml | 8 + .../Tests/Fixtures/with_define_path_variable.php | 5 + .../symfony/routing/Tests/Fixtures/withdoctype.xml | 3 + .../Generator/Dumper/PhpGeneratorDumperTest.php | 181 ++ .../routing/Tests/Generator/UrlGeneratorTest.php | 724 ++++++ .../Tests/Loader/AbstractAnnotationLoaderTest.php | 33 + .../Tests/Loader/AnnotationClassLoaderTest.php | 255 +++ .../Tests/Loader/AnnotationDirectoryLoaderTest.php | 98 + .../Tests/Loader/AnnotationFileLoaderTest.php | 91 + .../routing/Tests/Loader/ClosureLoaderTest.php | 49 + .../routing/Tests/Loader/DirectoryLoaderTest.php | 74 + .../routing/Tests/Loader/GlobFileLoaderTest.php | 45 + .../routing/Tests/Loader/ObjectRouteLoaderTest.php | 123 ++ .../routing/Tests/Loader/PhpFileLoaderTest.php | 133 ++ .../routing/Tests/Loader/XmlFileLoaderTest.php | 385 ++++ .../routing/Tests/Loader/YamlFileLoaderTest.php | 206 ++ .../Matcher/DumpedRedirectableUrlMatcherTest.php | 43 + .../routing/Tests/Matcher/DumpedUrlMatcherTest.php | 48 + .../Tests/Matcher/Dumper/DumperCollectionTest.php | 34 + .../Tests/Matcher/Dumper/PhpMatcherDumperTest.php | 459 ++++ .../Matcher/Dumper/StaticPrefixCollectionTest.php | 175 ++ .../Tests/Matcher/RedirectableUrlMatcherTest.php | 124 ++ .../Tests/Matcher/TraceableUrlMatcherTest.php | 122 + .../routing/Tests/Matcher/UrlMatcherTest.php | 509 +++++ .../symfony/routing/Tests/RequestContextTest.php | 160 ++ .../routing/Tests/RouteCollectionBuilderTest.php | 364 +++ .../symfony/routing/Tests/RouteCollectionTest.php | 305 +++ .../symfony/routing/Tests/RouteCompilerTest.php | 389 ++++ .../php/vendor/symfony/routing/Tests/RouteTest.php | 258 +++ .../vendor/symfony/routing/Tests/RouterTest.php | 163 ++ assets/php/vendor/symfony/routing/composer.json | 56 + assets/php/vendor/symfony/routing/phpunit.xml.dist | 30 + 847 files changed, 101402 insertions(+) create mode 100644 assets/css/main.css create mode 100644 assets/icons/BurgerMenuShort.svg create mode 100644 assets/icons/ExploreGlobe.svg create mode 100644 assets/icons/ExploreGlobeOutline.svg create mode 100644 assets/icons/FriendFeedOutline.svg create mode 100644 assets/icons/MessageBubble.svg create mode 100644 assets/icons/MessageBubbleOutline.svg create mode 100644 assets/icons/UserGroupOutline.svg create mode 100644 assets/icons/UserOutline.svg create mode 100644 assets/js/chat.js create mode 100644 assets/js/main.js create mode 100644 assets/php/bin/WebChatServer.php create mode 100644 assets/php/bin/server.csr create mode 100644 assets/php/composer.json create mode 100644 assets/php/composer.lock create mode 100644 assets/php/src/ChatProcessor.php create mode 100644 assets/php/vendor/autoload.php create mode 100644 assets/php/vendor/cboden/ratchet/.gitignore create mode 100644 assets/php/vendor/cboden/ratchet/.travis.yml create mode 100644 assets/php/vendor/cboden/ratchet/CHANGELOG.md create mode 100644 assets/php/vendor/cboden/ratchet/LICENSE create mode 100644 assets/php/vendor/cboden/ratchet/Makefile create mode 100644 assets/php/vendor/cboden/ratchet/README.md create mode 100644 assets/php/vendor/cboden/ratchet/composer.json create mode 100644 assets/php/vendor/cboden/ratchet/phpunit.xml.dist create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/AbstractConnectionDecorator.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/App.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/ComponentInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/ConnectionInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Http/CloseResponseTrait.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServer.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServerInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Http/NoOpHttpServerController.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Http/OriginCheck.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Http/Router.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/MessageComponentInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/MessageInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Server/EchoServer.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Server/FlashPolicy.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoConnection.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoServer.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IpBlackList.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/HandlerInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/PhpBinaryHandler.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/PhpHandler.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Session/SessionProvider.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/Proxy/VirtualProxy.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/VirtualSessionStorage.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Exception.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/JsonException.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/ServerProtocol.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Topic.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/TopicManager.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampConnection.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServer.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServerInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/ConnContext.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageCallableInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageComponentInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsConnection.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServer.php create mode 100644 assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServerInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/autobahn/bin/fuzzingserver.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-all.json create mode 100644 assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-profile.json create mode 100644 assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-quick.json create mode 100644 assets/php/vendor/cboden/ratchet/tests/bootstrap.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Component.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Connection.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/ConnectionDecorator.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/WampComponent.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/NullComponent.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Wamp/Stub/WsWampServerInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/WebSocket/Stub/WsMessageComponentInterface.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/AbstractConnectionDecoratorTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpRequestParserTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpServerTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Http/OriginCheckTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Http/RouterTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Server/EchoServerTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Server/FlashPolicyComponentTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Server/IoConnectionTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Server/IoServerTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Server/IpBlackListComponentTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Session/Serialize/PhpHandlerTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Session/SessionComponentTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Wamp/ServerProtocolTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicManagerTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampConnectionTest.php create mode 100644 assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampServerTest.php create mode 100644 assets/php/vendor/composer/ClassLoader.php create mode 100644 assets/php/vendor/composer/LICENSE create mode 100644 assets/php/vendor/composer/autoload_classmap.php create mode 100644 assets/php/vendor/composer/autoload_files.php create mode 100644 assets/php/vendor/composer/autoload_namespaces.php create mode 100644 assets/php/vendor/composer/autoload_psr4.php create mode 100644 assets/php/vendor/composer/autoload_real.php create mode 100644 assets/php/vendor/composer/autoload_static.php create mode 100644 assets/php/vendor/composer/installed.json create mode 100644 assets/php/vendor/evenement/evenement/.gitignore create mode 100644 assets/php/vendor/evenement/evenement/.travis.yml create mode 100644 assets/php/vendor/evenement/evenement/CHANGELOG.md create mode 100644 assets/php/vendor/evenement/evenement/LICENSE create mode 100644 assets/php/vendor/evenement/evenement/README.md create mode 100644 assets/php/vendor/evenement/evenement/composer.json create mode 100644 assets/php/vendor/evenement/evenement/doc/00-intro.md create mode 100644 assets/php/vendor/evenement/evenement/doc/01-api.md create mode 100644 assets/php/vendor/evenement/evenement/doc/02-plugin-system.md create mode 100644 assets/php/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php create mode 100644 assets/php/vendor/evenement/evenement/examples/benchmark-emit-once.php create mode 100644 assets/php/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php create mode 100644 assets/php/vendor/evenement/evenement/examples/benchmark-emit.php create mode 100644 assets/php/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php create mode 100644 assets/php/vendor/evenement/evenement/phpunit.xml.dist create mode 100644 assets/php/vendor/evenement/evenement/src/Evenement/EventEmitter.php create mode 100644 assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php create mode 100644 assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php create mode 100644 assets/php/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php create mode 100644 assets/php/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php create mode 100644 assets/php/vendor/evenement/evenement/tests/Evenement/Tests/functions.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md create mode 100644 assets/php/vendor/guzzlehttp/psr7/LICENSE create mode 100644 assets/php/vendor/guzzlehttp/psr7/README.md create mode 100644 assets/php/vendor/guzzlehttp/psr7/composer.json create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/AppendStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/BufferStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/CachingStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/DroppingStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/FnStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/InflateStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/LimitStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/PumpStream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/Request.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/Response.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/Stream.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/Uri.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/UriResolver.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/functions.php create mode 100644 assets/php/vendor/guzzlehttp/psr7/src/functions_include.php create mode 100644 assets/php/vendor/nubs/random-name-generator/.coveralls.yml create mode 100644 assets/php/vendor/nubs/random-name-generator/.gitignore create mode 100644 assets/php/vendor/nubs/random-name-generator/.scrutinizer.yml create mode 100644 assets/php/vendor/nubs/random-name-generator/.travis.yml create mode 100644 assets/php/vendor/nubs/random-name-generator/CONTRIBUTING.md create mode 100644 assets/php/vendor/nubs/random-name-generator/Dockerfile.tests create mode 100644 assets/php/vendor/nubs/random-name-generator/LICENSE create mode 100644 assets/php/vendor/nubs/random-name-generator/README.md create mode 100755 assets/php/vendor/nubs/random-name-generator/build.php create mode 100644 assets/php/vendor/nubs/random-name-generator/composer.json create mode 100644 assets/php/vendor/nubs/random-name-generator/composer.lock create mode 100644 assets/php/vendor/nubs/random-name-generator/docker-compose.yml create mode 100644 assets/php/vendor/nubs/random-name-generator/phpunit.xml create mode 100644 assets/php/vendor/nubs/random-name-generator/src/AbstractGenerator.php create mode 100644 assets/php/vendor/nubs/random-name-generator/src/All.php create mode 100644 assets/php/vendor/nubs/random-name-generator/src/Alliteration.php create mode 100644 assets/php/vendor/nubs/random-name-generator/src/Generator.php create mode 100644 assets/php/vendor/nubs/random-name-generator/src/Vgng.php create mode 100644 assets/php/vendor/nubs/random-name-generator/src/adjectives.txt create mode 100644 assets/php/vendor/nubs/random-name-generator/src/nouns.txt create mode 100644 assets/php/vendor/nubs/random-name-generator/src/video_game_names.txt create mode 100644 assets/php/vendor/nubs/random-name-generator/tests/AllTest.php create mode 100644 assets/php/vendor/nubs/random-name-generator/tests/AlliterationTest.php create mode 100644 assets/php/vendor/nubs/random-name-generator/tests/VgngTest.php create mode 100644 assets/php/vendor/paragonie/random_compat/LICENSE create mode 100755 assets/php/vendor/paragonie/random_compat/build-phar.sh create mode 100644 assets/php/vendor/paragonie/random_compat/composer.json create mode 100644 assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey create mode 100644 assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc create mode 100644 assets/php/vendor/paragonie/random_compat/lib/byte_safe_strings.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/cast_to_int.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/error_polyfill.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/random.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php create mode 100644 assets/php/vendor/paragonie/random_compat/lib/random_int.php create mode 100644 assets/php/vendor/paragonie/random_compat/other/build_phar.php create mode 100644 assets/php/vendor/paragonie/random_compat/psalm-autoload.php create mode 100644 assets/php/vendor/paragonie/random_compat/psalm.xml create mode 100644 assets/php/vendor/psr/http-message/CHANGELOG.md create mode 100644 assets/php/vendor/psr/http-message/LICENSE create mode 100644 assets/php/vendor/psr/http-message/README.md create mode 100644 assets/php/vendor/psr/http-message/composer.json create mode 100644 assets/php/vendor/psr/http-message/src/MessageInterface.php create mode 100644 assets/php/vendor/psr/http-message/src/RequestInterface.php create mode 100644 assets/php/vendor/psr/http-message/src/ResponseInterface.php create mode 100644 assets/php/vendor/psr/http-message/src/ServerRequestInterface.php create mode 100644 assets/php/vendor/psr/http-message/src/StreamInterface.php create mode 100644 assets/php/vendor/psr/http-message/src/UploadedFileInterface.php create mode 100644 assets/php/vendor/psr/http-message/src/UriInterface.php create mode 100644 assets/php/vendor/ratchet/rfc6455/.gitignore create mode 100644 assets/php/vendor/ratchet/rfc6455/.travis.yml create mode 100644 assets/php/vendor/ratchet/rfc6455/LICENSE create mode 100644 assets/php/vendor/ratchet/rfc6455/README.md create mode 100644 assets/php/vendor/ratchet/rfc6455/composer.json create mode 100644 assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Handshake/RequestVerifier.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Messaging/Frame.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Messaging/Message.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php create mode 100644 assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/AbResultsTest.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/ab/clientRunner.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingclient.json create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingserver.json create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/ab/run_ab_tests.sh create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/ab/startServer.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/bootstrap.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/RequestVerifierTest.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ResponseVerifierTest.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ServerNegotiatorTest.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/FrameTest.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageBufferTest.php create mode 100644 assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageTest.php create mode 100644 assets/php/vendor/react/cache/.gitignore create mode 100644 assets/php/vendor/react/cache/.travis.yml create mode 100644 assets/php/vendor/react/cache/CHANGELOG.md create mode 100644 assets/php/vendor/react/cache/LICENSE create mode 100644 assets/php/vendor/react/cache/README.md create mode 100644 assets/php/vendor/react/cache/composer.json create mode 100644 assets/php/vendor/react/cache/phpunit.xml.dist create mode 100644 assets/php/vendor/react/cache/src/ArrayCache.php create mode 100644 assets/php/vendor/react/cache/src/CacheInterface.php create mode 100644 assets/php/vendor/react/cache/tests/ArrayCacheTest.php create mode 100644 assets/php/vendor/react/cache/tests/CallableStub.php create mode 100644 assets/php/vendor/react/cache/tests/TestCase.php create mode 100644 assets/php/vendor/react/dns/.gitignore create mode 100644 assets/php/vendor/react/dns/.travis.yml create mode 100644 assets/php/vendor/react/dns/CHANGELOG.md create mode 100644 assets/php/vendor/react/dns/LICENSE create mode 100644 assets/php/vendor/react/dns/README.md create mode 100644 assets/php/vendor/react/dns/composer.json create mode 100644 assets/php/vendor/react/dns/examples/01-one.php create mode 100644 assets/php/vendor/react/dns/examples/02-concurrent.php create mode 100644 assets/php/vendor/react/dns/examples/03-cached.php create mode 100644 assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php create mode 100644 assets/php/vendor/react/dns/phpunit.xml.dist create mode 100644 assets/php/vendor/react/dns/src/BadServerException.php create mode 100644 assets/php/vendor/react/dns/src/Config/Config.php create mode 100644 assets/php/vendor/react/dns/src/Config/FilesystemFactory.php create mode 100644 assets/php/vendor/react/dns/src/Config/HostsFile.php create mode 100644 assets/php/vendor/react/dns/src/Model/HeaderBag.php create mode 100644 assets/php/vendor/react/dns/src/Model/Message.php create mode 100644 assets/php/vendor/react/dns/src/Model/Record.php create mode 100644 assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php create mode 100644 assets/php/vendor/react/dns/src/Protocol/Parser.php create mode 100644 assets/php/vendor/react/dns/src/Query/CachedExecutor.php create mode 100644 assets/php/vendor/react/dns/src/Query/CancellationException.php create mode 100644 assets/php/vendor/react/dns/src/Query/Executor.php create mode 100644 assets/php/vendor/react/dns/src/Query/ExecutorInterface.php create mode 100644 assets/php/vendor/react/dns/src/Query/HostsFileExecutor.php create mode 100644 assets/php/vendor/react/dns/src/Query/Query.php create mode 100644 assets/php/vendor/react/dns/src/Query/RecordBag.php create mode 100644 assets/php/vendor/react/dns/src/Query/RecordCache.php create mode 100644 assets/php/vendor/react/dns/src/Query/RetryExecutor.php create mode 100644 assets/php/vendor/react/dns/src/Query/TimeoutException.php create mode 100644 assets/php/vendor/react/dns/src/Query/TimeoutExecutor.php create mode 100644 assets/php/vendor/react/dns/src/RecordNotFoundException.php create mode 100644 assets/php/vendor/react/dns/src/Resolver/Factory.php create mode 100644 assets/php/vendor/react/dns/src/Resolver/Resolver.php create mode 100644 assets/php/vendor/react/dns/tests/CallableStub.php create mode 100644 assets/php/vendor/react/dns/tests/Config/ConfigTest.php create mode 100644 assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php create mode 100644 assets/php/vendor/react/dns/tests/Config/HostsFileTest.php create mode 100644 assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf create mode 100644 assets/php/vendor/react/dns/tests/FunctionalResolverTest.php create mode 100644 assets/php/vendor/react/dns/tests/Model/MessageTest.php create mode 100644 assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php create mode 100644 assets/php/vendor/react/dns/tests/Protocol/ParserTest.php create mode 100644 assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php create mode 100644 assets/php/vendor/react/dns/tests/Query/ExecutorTest.php create mode 100644 assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php create mode 100644 assets/php/vendor/react/dns/tests/Query/RecordBagTest.php create mode 100644 assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php create mode 100644 assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php create mode 100644 assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php create mode 100644 assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php create mode 100644 assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php create mode 100644 assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php create mode 100644 assets/php/vendor/react/dns/tests/TestCase.php create mode 100644 assets/php/vendor/react/event-loop/.gitignore create mode 100644 assets/php/vendor/react/event-loop/.travis.yml create mode 100644 assets/php/vendor/react/event-loop/CHANGELOG.md create mode 100644 assets/php/vendor/react/event-loop/LICENSE create mode 100644 assets/php/vendor/react/event-loop/README.md create mode 100644 assets/php/vendor/react/event-loop/composer.json create mode 100644 assets/php/vendor/react/event-loop/examples/01-timers.php create mode 100644 assets/php/vendor/react/event-loop/examples/02-periodic.php create mode 100644 assets/php/vendor/react/event-loop/examples/03-ticks.php create mode 100644 assets/php/vendor/react/event-loop/examples/04-signals.php create mode 100644 assets/php/vendor/react/event-loop/examples/11-consume-stdin.php create mode 100644 assets/php/vendor/react/event-loop/examples/12-generate-yes.php create mode 100644 assets/php/vendor/react/event-loop/examples/13-http-client-blocking.php create mode 100644 assets/php/vendor/react/event-loop/examples/14-http-client-async.php create mode 100644 assets/php/vendor/react/event-loop/examples/21-http-server.php create mode 100644 assets/php/vendor/react/event-loop/examples/91-benchmark-ticks.php create mode 100644 assets/php/vendor/react/event-loop/examples/92-benchmark-timers.php create mode 100644 assets/php/vendor/react/event-loop/examples/93-benchmark-ticks-delay.php create mode 100644 assets/php/vendor/react/event-loop/examples/94-benchmark-timers-delay.php create mode 100644 assets/php/vendor/react/event-loop/examples/95-benchmark-memory.php create mode 100644 assets/php/vendor/react/event-loop/phpunit.xml.dist create mode 100644 assets/php/vendor/react/event-loop/src/ExtEvLoop.php create mode 100644 assets/php/vendor/react/event-loop/src/ExtEventLoop.php create mode 100644 assets/php/vendor/react/event-loop/src/ExtLibevLoop.php create mode 100644 assets/php/vendor/react/event-loop/src/ExtLibeventLoop.php create mode 100644 assets/php/vendor/react/event-loop/src/Factory.php create mode 100644 assets/php/vendor/react/event-loop/src/LoopInterface.php create mode 100644 assets/php/vendor/react/event-loop/src/SignalsHandler.php create mode 100644 assets/php/vendor/react/event-loop/src/StreamSelectLoop.php create mode 100644 assets/php/vendor/react/event-loop/src/Tick/FutureTickQueue.php create mode 100644 assets/php/vendor/react/event-loop/src/Timer/Timer.php create mode 100644 assets/php/vendor/react/event-loop/src/Timer/Timers.php create mode 100644 assets/php/vendor/react/event-loop/src/TimerInterface.php create mode 100644 assets/php/vendor/react/event-loop/tests/AbstractLoopTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/CallableStub.php create mode 100644 assets/php/vendor/react/event-loop/tests/ExtEvLoopTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/ExtEventLoopTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/ExtLibevLoopTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/ExtLibeventLoopTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/SignalsHandlerTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/StreamSelectLoopTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/TestCase.php create mode 100644 assets/php/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/Timer/ExtEvTimerTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/Timer/ExtLibevTimerTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/Timer/ExtLibeventTimerTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/Timer/TimersTest.php create mode 100644 assets/php/vendor/react/event-loop/tests/bootstrap.php create mode 100755 assets/php/vendor/react/event-loop/travis-init.sh create mode 100644 assets/php/vendor/react/promise-timer/.gitignore create mode 100644 assets/php/vendor/react/promise-timer/.travis.yml create mode 100644 assets/php/vendor/react/promise-timer/CHANGELOG.md create mode 100644 assets/php/vendor/react/promise-timer/LICENSE create mode 100644 assets/php/vendor/react/promise-timer/README.md create mode 100644 assets/php/vendor/react/promise-timer/composer.json create mode 100644 assets/php/vendor/react/promise-timer/phpunit.xml.dist create mode 100644 assets/php/vendor/react/promise-timer/src/TimeoutException.php create mode 100644 assets/php/vendor/react/promise-timer/src/functions.php create mode 100644 assets/php/vendor/react/promise-timer/tests/CallableStub.php create mode 100644 assets/php/vendor/react/promise-timer/tests/FunctionRejectTest.php create mode 100644 assets/php/vendor/react/promise-timer/tests/FunctionResolveTest.php create mode 100644 assets/php/vendor/react/promise-timer/tests/FunctionTimeoutTest.php create mode 100644 assets/php/vendor/react/promise-timer/tests/TestCase.php create mode 100644 assets/php/vendor/react/promise-timer/tests/TimeoutExceptionTest.php create mode 100644 assets/php/vendor/react/promise/.gitignore create mode 100644 assets/php/vendor/react/promise/.travis.yml create mode 100644 assets/php/vendor/react/promise/CHANGELOG.md create mode 100644 assets/php/vendor/react/promise/LICENSE create mode 100644 assets/php/vendor/react/promise/README.md create mode 100644 assets/php/vendor/react/promise/composer.json create mode 100644 assets/php/vendor/react/promise/phpunit.xml.dist create mode 100644 assets/php/vendor/react/promise/src/CancellablePromiseInterface.php create mode 100644 assets/php/vendor/react/promise/src/CancellationQueue.php create mode 100644 assets/php/vendor/react/promise/src/Deferred.php create mode 100644 assets/php/vendor/react/promise/src/Exception/LengthException.php create mode 100644 assets/php/vendor/react/promise/src/ExtendedPromiseInterface.php create mode 100644 assets/php/vendor/react/promise/src/FulfilledPromise.php create mode 100644 assets/php/vendor/react/promise/src/LazyPromise.php create mode 100644 assets/php/vendor/react/promise/src/Promise.php create mode 100644 assets/php/vendor/react/promise/src/PromiseInterface.php create mode 100644 assets/php/vendor/react/promise/src/PromisorInterface.php create mode 100644 assets/php/vendor/react/promise/src/RejectedPromise.php create mode 100644 assets/php/vendor/react/promise/src/UnhandledRejectionException.php create mode 100644 assets/php/vendor/react/promise/src/functions.php create mode 100644 assets/php/vendor/react/promise/src/functions_include.php create mode 100644 assets/php/vendor/react/promise/tests/CancellationQueueTest.php create mode 100644 assets/php/vendor/react/promise/tests/DeferredTest.php create mode 100644 assets/php/vendor/react/promise/tests/FulfilledPromiseTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionAllTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionAnyTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionCheckTypehintTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionMapTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionRaceTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionReduceTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionRejectTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionResolveTest.php create mode 100644 assets/php/vendor/react/promise/tests/FunctionSomeTest.php create mode 100644 assets/php/vendor/react/promise/tests/LazyPromiseTest.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/FullTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/NotifyTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php create mode 100644 assets/php/vendor/react/promise/tests/RejectedPromiseTest.php create mode 100644 assets/php/vendor/react/promise/tests/Stub/CallableStub.php create mode 100644 assets/php/vendor/react/promise/tests/TestCase.php create mode 100644 assets/php/vendor/react/promise/tests/bootstrap.php create mode 100644 assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php create mode 100644 assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestThenable.php create mode 100644 assets/php/vendor/react/promise/tests/fixtures/SimpleRejectedTestPromise.php create mode 100644 assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellable.php create mode 100644 assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php create mode 100644 assets/php/vendor/react/socket/.gitignore create mode 100644 assets/php/vendor/react/socket/.travis.yml create mode 100644 assets/php/vendor/react/socket/CHANGELOG.md create mode 100644 assets/php/vendor/react/socket/LICENSE create mode 100644 assets/php/vendor/react/socket/README.md create mode 100644 assets/php/vendor/react/socket/composer.json create mode 100644 assets/php/vendor/react/socket/examples/01-echo-server.php create mode 100644 assets/php/vendor/react/socket/examples/02-chat-server.php create mode 100644 assets/php/vendor/react/socket/examples/03-http-server.php create mode 100644 assets/php/vendor/react/socket/examples/11-http-client.php create mode 100644 assets/php/vendor/react/socket/examples/12-https-client.php create mode 100644 assets/php/vendor/react/socket/examples/21-netcat-client.php create mode 100644 assets/php/vendor/react/socket/examples/22-http-client.php create mode 100644 assets/php/vendor/react/socket/examples/91-benchmark-server.php create mode 100644 assets/php/vendor/react/socket/examples/99-generate-self-signed.php create mode 100644 assets/php/vendor/react/socket/examples/localhost.pem create mode 100644 assets/php/vendor/react/socket/examples/localhost_swordfish.pem create mode 100644 assets/php/vendor/react/socket/phpunit.xml.dist create mode 100644 assets/php/vendor/react/socket/src/Connection.php create mode 100644 assets/php/vendor/react/socket/src/ConnectionInterface.php create mode 100644 assets/php/vendor/react/socket/src/Connector.php create mode 100644 assets/php/vendor/react/socket/src/ConnectorInterface.php create mode 100644 assets/php/vendor/react/socket/src/DnsConnector.php create mode 100644 assets/php/vendor/react/socket/src/FixedUriConnector.php create mode 100644 assets/php/vendor/react/socket/src/LimitingServer.php create mode 100644 assets/php/vendor/react/socket/src/SecureConnector.php create mode 100644 assets/php/vendor/react/socket/src/SecureServer.php create mode 100644 assets/php/vendor/react/socket/src/Server.php create mode 100644 assets/php/vendor/react/socket/src/ServerInterface.php create mode 100644 assets/php/vendor/react/socket/src/StreamEncryption.php create mode 100644 assets/php/vendor/react/socket/src/TcpConnector.php create mode 100644 assets/php/vendor/react/socket/src/TcpServer.php create mode 100644 assets/php/vendor/react/socket/src/TimeoutConnector.php create mode 100644 assets/php/vendor/react/socket/src/UnixConnector.php create mode 100644 assets/php/vendor/react/socket/src/UnixServer.php create mode 100644 assets/php/vendor/react/socket/tests/ConnectionTest.php create mode 100644 assets/php/vendor/react/socket/tests/ConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/DnsConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/FixedUriConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/FunctionalConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/FunctionalSecureServerTest.php create mode 100644 assets/php/vendor/react/socket/tests/FunctionalTcpServerTest.php create mode 100644 assets/php/vendor/react/socket/tests/IntegrationTest.php create mode 100644 assets/php/vendor/react/socket/tests/LimitingServerTest.php create mode 100644 assets/php/vendor/react/socket/tests/SecureConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/SecureIntegrationTest.php create mode 100644 assets/php/vendor/react/socket/tests/SecureServerTest.php create mode 100644 assets/php/vendor/react/socket/tests/ServerTest.php create mode 100644 assets/php/vendor/react/socket/tests/Stub/CallableStub.php create mode 100644 assets/php/vendor/react/socket/tests/Stub/ConnectionStub.php create mode 100644 assets/php/vendor/react/socket/tests/Stub/ServerStub.php create mode 100644 assets/php/vendor/react/socket/tests/TcpConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/TcpServerTest.php create mode 100644 assets/php/vendor/react/socket/tests/TestCase.php create mode 100644 assets/php/vendor/react/socket/tests/TimeoutConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/UnixConnectorTest.php create mode 100644 assets/php/vendor/react/socket/tests/UnixServerTest.php create mode 100644 assets/php/vendor/react/stream/.gitignore create mode 100644 assets/php/vendor/react/stream/.travis.yml create mode 100644 assets/php/vendor/react/stream/CHANGELOG.md create mode 100644 assets/php/vendor/react/stream/LICENSE create mode 100644 assets/php/vendor/react/stream/README.md create mode 100644 assets/php/vendor/react/stream/composer.json create mode 100644 assets/php/vendor/react/stream/examples/01-http.php create mode 100644 assets/php/vendor/react/stream/examples/02-https.php create mode 100644 assets/php/vendor/react/stream/examples/11-cat.php create mode 100644 assets/php/vendor/react/stream/examples/91-benchmark-throughput.php create mode 100644 assets/php/vendor/react/stream/phpunit.xml.dist create mode 100644 assets/php/vendor/react/stream/src/CompositeStream.php create mode 100644 assets/php/vendor/react/stream/src/DuplexResourceStream.php create mode 100644 assets/php/vendor/react/stream/src/DuplexStreamInterface.php create mode 100644 assets/php/vendor/react/stream/src/ReadableResourceStream.php create mode 100644 assets/php/vendor/react/stream/src/ReadableStreamInterface.php create mode 100644 assets/php/vendor/react/stream/src/ThroughStream.php create mode 100644 assets/php/vendor/react/stream/src/Util.php create mode 100644 assets/php/vendor/react/stream/src/WritableResourceStream.php create mode 100644 assets/php/vendor/react/stream/src/WritableStreamInterface.php create mode 100644 assets/php/vendor/react/stream/tests/CallableStub.php create mode 100644 assets/php/vendor/react/stream/tests/CompositeStreamTest.php create mode 100644 assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php create mode 100644 assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php create mode 100644 assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php create mode 100644 assets/php/vendor/react/stream/tests/FunctionalInternetTest.php create mode 100644 assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php create mode 100644 assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php create mode 100644 assets/php/vendor/react/stream/tests/TestCase.php create mode 100644 assets/php/vendor/react/stream/tests/ThroughStreamTest.php create mode 100644 assets/php/vendor/react/stream/tests/UtilTest.php create mode 100644 assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/.gitignore create mode 100644 assets/php/vendor/symfony/http-foundation/AcceptHeader.php create mode 100644 assets/php/vendor/symfony/http-foundation/AcceptHeaderItem.php create mode 100644 assets/php/vendor/symfony/http-foundation/ApacheRequest.php create mode 100644 assets/php/vendor/symfony/http-foundation/BinaryFileResponse.php create mode 100644 assets/php/vendor/symfony/http-foundation/CHANGELOG.md create mode 100644 assets/php/vendor/symfony/http-foundation/Cookie.php create mode 100644 assets/php/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php create mode 100644 assets/php/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php create mode 100644 assets/php/vendor/symfony/http-foundation/ExpressionRequestMatcher.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/Exception/FileException.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/Exception/UploadException.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/File.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/Stream.php create mode 100644 assets/php/vendor/symfony/http-foundation/File/UploadedFile.php create mode 100644 assets/php/vendor/symfony/http-foundation/FileBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/HeaderBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/IpUtils.php create mode 100644 assets/php/vendor/symfony/http-foundation/JsonResponse.php create mode 100644 assets/php/vendor/symfony/http-foundation/LICENSE create mode 100644 assets/php/vendor/symfony/http-foundation/ParameterBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/README.md create mode 100644 assets/php/vendor/symfony/http-foundation/RedirectResponse.php create mode 100644 assets/php/vendor/symfony/http-foundation/Request.php create mode 100644 assets/php/vendor/symfony/http-foundation/RequestMatcher.php create mode 100644 assets/php/vendor/symfony/http-foundation/RequestMatcherInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/RequestStack.php create mode 100644 assets/php/vendor/symfony/http-foundation/Response.php create mode 100644 assets/php/vendor/symfony/http-foundation/ResponseHeaderBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/ServerBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Session.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/SessionBagInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/SessionBagProxy.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/SessionInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php create mode 100644 assets/php/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php create mode 100644 assets/php/vendor/symfony/http-foundation/StreamedResponse.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/CookieTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/FakeFile.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/FileTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/FileBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/HeaderBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/IpUtilsTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/JsonResponseTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/ParameterBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/RequestStackTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/RequestTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/ResponseTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/ResponseTestCase.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/ServerBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/SessionTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng create mode 100644 assets/php/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng create mode 100644 assets/php/vendor/symfony/http-foundation/composer.json create mode 100644 assets/php/vendor/symfony/http-foundation/phpunit.xml.dist create mode 100644 assets/php/vendor/symfony/polyfill-mbstring/LICENSE create mode 100644 assets/php/vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 assets/php/vendor/symfony/polyfill-mbstring/README.md create mode 100644 assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 assets/php/vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 assets/php/vendor/symfony/polyfill-mbstring/composer.json create mode 100644 assets/php/vendor/symfony/polyfill-php70/LICENSE create mode 100644 assets/php/vendor/symfony/polyfill-php70/Php70.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/README.md create mode 100644 assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/Resources/stubs/Error.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/bootstrap.php create mode 100644 assets/php/vendor/symfony/polyfill-php70/composer.json create mode 100644 assets/php/vendor/symfony/routing/.gitignore create mode 100644 assets/php/vendor/symfony/routing/Annotation/Route.php create mode 100644 assets/php/vendor/symfony/routing/CHANGELOG.md create mode 100644 assets/php/vendor/symfony/routing/CompiledRoute.php create mode 100644 assets/php/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php create mode 100644 assets/php/vendor/symfony/routing/Exception/ExceptionInterface.php create mode 100644 assets/php/vendor/symfony/routing/Exception/InvalidParameterException.php create mode 100644 assets/php/vendor/symfony/routing/Exception/MethodNotAllowedException.php create mode 100644 assets/php/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php create mode 100644 assets/php/vendor/symfony/routing/Exception/NoConfigurationException.php create mode 100644 assets/php/vendor/symfony/routing/Exception/ResourceNotFoundException.php create mode 100644 assets/php/vendor/symfony/routing/Exception/RouteNotFoundException.php create mode 100644 assets/php/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php create mode 100644 assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php create mode 100644 assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php create mode 100644 assets/php/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php create mode 100644 assets/php/vendor/symfony/routing/Generator/UrlGenerator.php create mode 100644 assets/php/vendor/symfony/routing/Generator/UrlGeneratorInterface.php create mode 100644 assets/php/vendor/symfony/routing/LICENSE create mode 100644 assets/php/vendor/symfony/routing/Loader/AnnotationClassLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/AnnotationFileLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/ClosureLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php create mode 100644 assets/php/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php create mode 100644 assets/php/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php create mode 100644 assets/php/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php create mode 100644 assets/php/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php create mode 100644 assets/php/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php create mode 100644 assets/php/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/DirectoryLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/GlobFileLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/ObjectRouteLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/PhpFileLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/XmlFileLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/YamlFileLoader.php create mode 100644 assets/php/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd create mode 100644 assets/php/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/RequestMatcherInterface.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/UrlMatcher.php create mode 100644 assets/php/vendor/symfony/routing/Matcher/UrlMatcherInterface.php create mode 100644 assets/php/vendor/symfony/routing/README.md create mode 100644 assets/php/vendor/symfony/routing/RequestContext.php create mode 100644 assets/php/vendor/symfony/routing/RequestContextAwareInterface.php create mode 100644 assets/php/vendor/symfony/routing/Route.php create mode 100644 assets/php/vendor/symfony/routing/RouteCollection.php create mode 100644 assets/php/vendor/symfony/routing/RouteCollectionBuilder.php create mode 100644 assets/php/vendor/symfony/routing/RouteCompiler.php create mode 100644 assets/php/vendor/symfony/routing/RouteCompilerInterface.php create mode 100644 assets/php/vendor/symfony/routing/Router.php create mode 100644 assets/php/vendor/symfony/routing/RouterInterface.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Annotation/RouteTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/CompiledRouteTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/annotated.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/bad_format.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/bar.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/empty.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/file_resource.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/foo.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/foo1.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/incomplete.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/missing_id.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/missing_path.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/null_values.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.yml create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Fixtures/withdoctype.xml create mode 100644 assets/php/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/RequestContextTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/RouteCollectionTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/RouteCompilerTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/RouteTest.php create mode 100644 assets/php/vendor/symfony/routing/Tests/RouterTest.php create mode 100644 assets/php/vendor/symfony/routing/composer.json create mode 100644 assets/php/vendor/symfony/routing/phpunit.xml.dist (limited to 'assets') diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..38ecd23 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,172 @@ +/****** +GENERAL +******/ +* { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-overflow-scrolling: touch; + + outline: none; + ie-dummy: expression(this.hideFocus=true); +} + +body { + font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; + height: 100%; + width: 100%; + overflow: hidden; + background-color: #0B1D33; + color: #426A91; + margin: 0; + padding: 0; + border: 0; +} + +.main { + height: 100%; +} + +hr { + width: 100%; + display: block; + height: 1px; + border: 0; + border-top: 1px solid #112a42; + margin: 1em 0; + padding: 0; +} + +/******* +FLICKITY +*******/ +.main-carousel { + background: #EEE; +} + +.carousel-cell { + width: 100%; + height: -moz-calc(100vh - 75px); + height: -webkit-calc(100vh - 75px); + height: -o-calc(100vh - 75px); + height: calc(100vh - 75px); + background: #0B1D33; + counter-increment: carousel-cell; +} + +/****** +HEADER +******/ +.header { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + align-content: center; + margin: 20px; +} + +.LeftButtonHeader { + width: 20px; + height: 20px; + -webkit-filter: invert(.5); + -moz-filter: invert(.5); +} + +.HeaderCaption { + color: lightgrey; +} + +.RightButtonHeader { + width: 20px; + height: 20px; + -webkit-filter: invert(.5); + -moz-filter: invert(.5); +} + +/*********** +GENERAL TABS +***********/ +.MainInTab { + height: 100%; +} + +/********** +CHAT WINDOW +**********/ +.ChatWindow { + height: 100%; + position: relative; +} + +.ChatResponses { + +} + +.ChatInput { + position: absolute; + margin: auto; + bottom: 90px; /* 75+15 */ + left: 0; + z-index: 600; + width: 100%; + height: 40px; + border:none; + color: #FFF; + background-color: #13223C; +} + +#ChatTextInput { + display: none; +} + + +/***** +NAVBAR +*****/ +.Navbar { + display: flex; + flex-wrap: nowrap; + justify-content: center; + align-content: center; + position: absolute; + bottom: 0; + left: 0; + height: 75px; + width: 100%; + background-color: #13223C; +} + +.NavbarIconWrap { + margin: auto; +} + +.NavbarIconWrap img { + height: 30px; + width: 30px; + filter: invert(.5); + -webkit-filter: invert(.5); + -moz-filter: invert(.5); +} + +.NavbarLine { + position: absolute; + bottom: 73px; + left: 0; + width: 20% !important; + height: 2px; + background: #FF00A2; +} + +.ActiveTab { + -webkit-transition: -moz-transform .3s ease-out; + -moz-transition: -webkit-transform .3s ease-out; + + filter: invert(16%) sepia(77%) saturate(6174%) hue-rotate(316deg) brightness(107%) contrast(110%); + -webkit-filter: invert(16%) sepia(77%) saturate(6174%) hue-rotate(316deg) brightness(107%) contrast(110%); + -moz-filter: invert(16%) sepia(77%) saturate(6174%) hue-rotate(316deg) brightness(107%) contrast(110%); +} diff --git a/assets/icons/BurgerMenuShort.svg b/assets/icons/BurgerMenuShort.svg new file mode 100644 index 0000000..1bf78bd --- /dev/null +++ b/assets/icons/BurgerMenuShort.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/ExploreGlobe.svg b/assets/icons/ExploreGlobe.svg new file mode 100644 index 0000000..6778254 --- /dev/null +++ b/assets/icons/ExploreGlobe.svg @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/ExploreGlobeOutline.svg b/assets/icons/ExploreGlobeOutline.svg new file mode 100644 index 0000000..6778254 --- /dev/null +++ b/assets/icons/ExploreGlobeOutline.svg @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/FriendFeedOutline.svg b/assets/icons/FriendFeedOutline.svg new file mode 100644 index 0000000..4243deb --- /dev/null +++ b/assets/icons/FriendFeedOutline.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/MessageBubble.svg b/assets/icons/MessageBubble.svg new file mode 100644 index 0000000..81ea5cc --- /dev/null +++ b/assets/icons/MessageBubble.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/MessageBubbleOutline.svg b/assets/icons/MessageBubbleOutline.svg new file mode 100644 index 0000000..81ea5cc --- /dev/null +++ b/assets/icons/MessageBubbleOutline.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/UserGroupOutline.svg b/assets/icons/UserGroupOutline.svg new file mode 100644 index 0000000..ca8c11c --- /dev/null +++ b/assets/icons/UserGroupOutline.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/UserOutline.svg b/assets/icons/UserOutline.svg new file mode 100644 index 0000000..6051129 --- /dev/null +++ b/assets/icons/UserOutline.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/js/chat.js b/assets/js/chat.js new file mode 100644 index 0000000..1ea3471 --- /dev/null +++ b/assets/js/chat.js @@ -0,0 +1,33 @@ +var conn = new WebSocket('wss://marvinborner.ddnss.de:1337'); +conn.onopen = function () { + console.log("Chat connection established!"); +}; + +conn.onmessage = function (e) { + document.getElementById("ChatResponses").innerHTML += e.data + "
"; +}; + +$('#ChatTextInput').keyup(function (e) { + if (e.keyCode === 13) { + sendMessage($('#ChatTextInput').val()); + $('#ChatTextInput').val(""); + } +}); + +$('#SubscribeTextInput').keyup(function (e) { + if (e.keyCode === 13) { + subscribe($('#SubscribeTextInput').val()); + } +}); + +function subscribe(channel) { + conn.send(JSON.stringify({command: "subscribe", channel: channel})); + $("#SubscribeTextInput").hide(); + $("#ChatTextInput").show(); + $("#ChatResponses").empty(); +} + +function sendMessage(msg) { + conn.send(JSON.stringify({command: "message", message: msg})); + $("#ChatTextInput").val(""); +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..b9cfe34 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,93 @@ +/****** + GENERAL + ******/ + +/***** + NAVBAR + *****/ +var $el, leftPos, newWidth, + $mainNav = $(".Navbar"); +$mainNav.append(""); +var $magicLine = $(".NavbarLine"); +$magicLine + .css("left", $(".ActiveTab").position().left) + .data("origLeft", $magicLine.position().left) + .data("origWidth", $magicLine.width()); +$(".NavbarIconWrap").on("click", function () { + $(".NavbarIconWrap").removeClass("ActiveTab"); + $(this).addClass("ActiveTab"); + var index = $(this).attr('id'); + $('.MainTabWindows').slick('slickGoTo',index); + //$('.MainTabWindows').flickity().flickity('select', index); + + $el = $(this); + leftPos = $el.position().left; + $magicLine.stop().animate({ + left: leftPos, + width: newWidth, + }, 300); +}); + +/******* +FLICKITY +*******/ +$('.MainTabWindows').slick({ + initialSlide: 2, + mobileFirst: true, + nextArrow: "", + prevArrow: "", + infinite: false, + zIndex: 500 +}); + +$('.MainTabWindows').on('beforeChange', function(event, slick, currentSlide, nextSlide){ + //console.log(nextSlide); + $(".NavbarIconWrap").removeClass("ActiveTab"); + $el = $("#" + nextSlide); + $el.addClass("ActiveTab"); + leftPos = $el.position().left; + $magicLine.stop().animate({ + left: leftPos, + width: newWidth + }, 300); +}); + +/* +$('.MainTabWindows').flickity({ + cellAlign: 'left', + prevNextButtons: false, + pageDots: false, + friction: 0.3, + dragThreshold: ($("body").width() * 0.5), + initialIndex: 2, + wrapAround: true, + maxSwipeWidth: 0, + on: { + change: function (index) { + $(".NavbarIconWrap").removeClass("ActiveTab"); + $el = $("#" + index); + $el.addClass("ActiveTab"); + leftPos = $el.position().left; + $magicLine.stop().animate({ + left: leftPos, + width: newWidth + }, 300); + }, + dragStart: function () { + $(".ActiveTab").css({ transform: 'scale(1.05)' }); + }, + dragEnd: function () { + $(".NavbarIconWrap").css({ transform: 'scale(1.0)' }); + }/*, + scroll: function (event, progress) { + var TotalWidth = $("body").width(); + console.log(progress / 10); + leftPos = ((progress / 1000) * TotalWidth + 'px'); + $magicLine.stop().animate({ + left: leftPos, + width: newWidth + }); + }* + } +}); +*/ \ No newline at end of file diff --git a/assets/php/bin/WebChatServer.php b/assets/php/bin/WebChatServer.php new file mode 100644 index 0000000..bebd7b9 --- /dev/null +++ b/assets/php/bin/WebChatServer.php @@ -0,0 +1,18 @@ +run(); diff --git a/assets/php/bin/server.csr b/assets/php/bin/server.csr new file mode 100644 index 0000000..8523b82 --- /dev/null +++ b/assets/php/bin/server.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICzTCCAbUCAQAwgYcxCzAJBgNVBAYTAkRFMRMwEQYDVQQIDApTb21lLVN0YXRl +MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxHjAcBgNVBAMMFW1h +cnZpbmJvcm5lci5kZG5zcy5kZTEgMB4GCSqGSIb3DQEJARYRbWFydmluQGJvcm5l +cnMuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjNPmJb5tz9xp+ +h9Jt7Go+VQ31oEu/FINxxW9jamPvvAzcBs82YKoLuOb1NHJX0wWLDAoCwXRn/vpP +pp2OPicl4ACFKOUoKLUj4XXpsFMNsfV5qT4z0rte8P3RYcwbVWlscNekVMbgA6DF +PXvOqZWHgJEZe8UoVP0Pj9J7/xcjY+VpfBhHAEimOipBVxijFAtnOzJ2o1M58xjZ +3XH2BmeSVbjmhSgIbB50zcATKcVudSvaj3hnGmnfwhPRpO2UOvBrrQubwRoTGyvV +TsW6dGGVY/qOJIyBcddayaZJraozBjDwp43c9S44DSotEgeLtvAyceQ27WDl37bl +eEEyMJpvAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEATQ8QnGSJmoudHnYuG8t2 +SHQOxp4R3RcsgXRtXSm3tFlIAvvvixqiqVBfppxK6f6cf4B8tagmXNdblCK8dbWn +9OIBxEh/hrooCtzydXcfvaJaFFaqPQ5YMyNNTtwLHd14w2hSSixNcON+YjfVY2ZA +CdlR/pEDv/xSCABaTNecvSUYBHWsbmo7pMSKBZ/wI0UcgJHbB4PhCjuLTdB79RTp +RJsMzA9ISZd6dLFxkmnOgmq2+sX3xTRK5XSrNfkE9Veou26BHeCwSBA25JVkatqC +DaSuNw3a9SY28Tk1GPjhrKaX8lI40aViWtnuwW36jD3G2GMyXbo4K8hST0wI6gHi +PQ== +-----END CERTIFICATE REQUEST----- diff --git a/assets/php/composer.json b/assets/php/composer.json new file mode 100644 index 0000000..7c3318b --- /dev/null +++ b/assets/php/composer.json @@ -0,0 +1,11 @@ +{ + "autoload": { + "psr-4": { + "Websocket\\": "src" + } + }, + "require": { + "cboden/ratchet": "^0.4.1", + "nubs/random-name-generator": "^2.1" + } +} diff --git a/assets/php/composer.lock b/assets/php/composer.lock new file mode 100644 index 0000000..a852b73 --- /dev/null +++ b/assets/php/composer.lock @@ -0,0 +1,943 @@ +{ + "_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": "7db98e9dbf466f436f9127ff8289eac0", + "packages": [ + { + "name": "cboden/ratchet", + "version": "v0.4.1", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/Ratchet.git", + "reference": "0d31f3a8ad4795fd48397712709e55cd07f51360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/0d31f3a8ad4795fd48397712709e55cd07f51360", + "reference": "0d31f3a8ad4795fd48397712709e55cd07f51360", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.0", + "php": ">=5.4.2", + "ratchet/rfc6455": "^0.2", + "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", + "symfony/http-foundation": "^2.6|^3.0|^4.0", + "symfony/routing": "^2.6|^3.0|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\": "src/Ratchet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + } + ], + "description": "PHP WebSocket library", + "homepage": "http://socketo.me", + "keywords": [ + "Ratchet", + "WebSockets", + "server", + "sockets", + "websocket" + ], + "time": "2017-12-12T00:49:31+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "time": "2017-07-23T21:35:13+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "nubs/random-name-generator", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/nubs/random-name-generator.git", + "reference": "7004eb1724e1c4a154553e44312b7045fe412b77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nubs/random-name-generator/zipball/7004eb1724e1c4a154553e44312b7045fe412b77", + "reference": "7004eb1724e1c4a154553e44312b7045fe412b77", + "shasum": "" + }, + "require": { + "php": "~5.6 || ~7.0" + }, + "require-dev": { + "cinam/randomizer": ">=1.1.1,<2.0", + "phpunit/phpunit": "~5.0", + "satooshi/php-coveralls": "~1.0", + "squizlabs/php_codesniffer": "~2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nubs\\RandomNameGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spencer Rinehart", + "email": "anubis@overthemonkey.com", + "role": "Developer" + } + ], + "description": "A library to create interesting, sometimes entertaining, random names.", + "keywords": [ + "alliteration", + "generator", + "random", + "video game" + ], + "time": "2016-12-04T01:57:19+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": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ratchet/rfc6455", + "version": "v0.2.3", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/RFC6455.git", + "reference": "cc8a1a46a703ce01de10fdb5fab387381b66edc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/cc8a1a46a703ce01de10fdb5fab387381b66edc8", + "reference": "cc8a1a46a703ce01de10fdb5fab387381b66edc8", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.0", + "php": ">=5.4.2" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "react/http": "^0.4.1", + "react/socket-client": "^0.4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\RFC6455\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + } + ], + "description": "RFC6455 WebSocket protocol handler", + "homepage": "http://socketo.me", + "keywords": [ + "WebSockets", + "rfc6455", + "websocket" + ], + "time": "2017-09-13T13:49:42+00:00" + }, + { + "name": "react/cache", + "version": "v0.4.2", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "75494f26b4ef089db9bf8c90b63c296246e099e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/75494f26b4ef089db9bf8c90b63c296246e099e8", + "reference": "75494f26b4ef089db9bf8c90b63c296246e099e8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "~2.0|~1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "time": "2017-12-20T16:47:13+00:00" + }, + { + "name": "react/dns", + "version": "v0.4.13", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7d1e08c300fd7de600810883386ee5e2a64898f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7d1e08c300fd7de600810883386ee5e2a64898f4", + "reference": "7d1e08c300fd7de600810883386ee5e2a64898f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "~0.4.0|~0.3.0", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "^2.1 || ^1.2.1", + "react/promise-timer": "^1.2", + "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "time": "2018-02-27T12:51:22+00:00" + }, + { + "name": "react/event-loop", + "version": "v0.5.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "e1e0647a5c6e2c86013a24e9c8252113df86105a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/e1e0647a5c6e2c86013a24e9c8252113df86105a", + "reference": "e1e0647a5c6e2c86013a24e9c8252113df86105a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" + }, + "suggest": { + "ext-event": "~1.0 for ExtEventLoop", + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "time": "2018-04-09T11:59:21+00:00" + }, + { + "name": "react/promise", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/62785ae604c8d69725d693eb370e1d67e94c4053", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "time": "2017-03-25T12:08:31+00:00" + }, + { + "name": "react/promise-timer", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "9b4cd9cbe7457e0d87fe8aa7ccceab8a2c830fbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/9b4cd9cbe7457e0d87fe8aa7ccceab8a2c830fbd", + "reference": "9b4cd9cbe7457e0d87fe8aa7ccceab8a2c830fbd", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "~2.1|~1.2" + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Promise\\Timer\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "homepage": "https://github.com/react/promise-timer", + "keywords": [ + "async", + "event-loop", + "promise", + "reactphp", + "timeout", + "timer" + ], + "time": "2017-12-22T15:41:41+00:00" + }, + { + "name": "react/socket", + "version": "v0.8.10", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "d3957313c92b539537fccc80170c05a27ec25796" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/d3957313c92b539537fccc80170c05a27ec25796", + "reference": "d3957313c92b539537fccc80170c05a27ec25796", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^0.4.13", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "^2.1 || ^1.2", + "react/promise-timer": "~1.0", + "react/stream": "^1.0 || ^0.7.1" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "time": "2018-02-28T09:32:38+00:00" + }, + { + "name": "react/stream", + "version": "v0.7.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "10100896018fd847a257cd81143b8e1b7be08e40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/10100896018fd847a257cd81143b8e1b7be08e40", + "reference": "10100896018fd847a257cd81143b8e1b7be08e40", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "time": "2018-01-19T15:04:38+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" + }, + { + "name": "symfony/routing", + "version": "v3.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "50f333b707bef9f6972ad04e6df3ec8875c9a67c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/50f333b707bef9f6972ad04e6df3ec8875c9a67c", + "reference": "50f333b707bef9f6972ad04e6df3ec8875c9a67c", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "^3.3.1|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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 Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2018-04-04T13:22:16+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/assets/php/src/ChatProcessor.php b/assets/php/src/ChatProcessor.php new file mode 100644 index 0000000..c43f968 --- /dev/null +++ b/assets/php/src/ChatProcessor.php @@ -0,0 +1,93 @@ +clients = new \SplObjectStorage; + $this->subscriptions = []; + $this->users = []; + $this->connectedUsersNames = []; + } + + public function onOpen(ConnectionInterface $conn) { + $generator = new \Nubs\RandomNameGenerator\Alliteration(); + $this->clients->attach($conn); + $this->users[$conn->resourceId] = $conn; + $this->connectedUsersNames[$conn->resourceId] = $generator->getName(); + + echo "New connection! ({$conn->resourceId})\n"; + } + + /*public function onMessage(ConnectionInterface $from, $msg) { + $numRecv = count($this->clients) - 1; + echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n" + , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's'); + + foreach ($this->clients as $client) { + if ($from === $client) { + $client->send("You - " . $msg); + } else { + $client->send("" . $from->resourceId . " - " . $msg); + } + } + } + */ + + public function onMessage(ConnectionInterface $conn, MessageInterface $msg) { + $data = json_decode($msg); + switch ($data->command) { + case "subscribe": + $this->subscriptions[$conn->resourceId] = $data->channel; + foreach ($this->subscriptions as $id => $channel) { + if ($this->subscriptions[$conn->resourceId] == $channel) { + if ($id === $conn->resourceId) { + $this->users[$id]->send("You (" . $this->connectedUsersNames[$conn->resourceId] . ") joined this group."); + } else { + $this->users[$id]->send("User (" . $this->connectedUsersNames[$conn->resourceId] . ") joined this group."); + } + } + } + break; + case "message": + if (isset($this->subscriptions[$conn->resourceId])) { + $target = $this->subscriptions[$conn->resourceId]; + foreach ($this->subscriptions as $id => $channel) { + if ($channel == $target && $id == $conn->resourceId) { + $this->users[$id]->send("You - " . $data->message); + } else if ($channel == $target && $id != $conn->resourceId) { + $this->users[$id]->send("" . $this->connectedUsersNames[$conn->resourceId] . " - " . $data->message); + } + } + } + } + } + + public function onClose(ConnectionInterface $conn) { + $this->clients->detach($conn); + echo "Connection {$conn->resourceId} has disconnected\n"; + foreach ($this->clients as $client) { + $client->send("User " . $this->connectedUsersNames[$conn->resourceId] . " has disconnected"); + } + unset($this->users[$conn->resourceId]); + unset($this->subscriptions[$conn->resourceId]); + unset($this->connectedUsersNames[$conn->resourceId]); + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + echo "An error has occurred: {$e->getMessage()}\n"; + + $conn->close(); + } +} \ No newline at end of file diff --git a/assets/php/vendor/autoload.php b/assets/php/vendor/autoload.php new file mode 100644 index 0000000..fca3b98 --- /dev/null +++ b/assets/php/vendor/autoload.php @@ -0,0 +1,7 @@ +> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' + - php -m + - composer install --dev --prefer-source diff --git a/assets/php/vendor/cboden/ratchet/CHANGELOG.md b/assets/php/vendor/cboden/ratchet/CHANGELOG.md new file mode 100644 index 0000000..5169993 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/CHANGELOG.md @@ -0,0 +1,135 @@ +CHANGELOG +========= + +### Legend + +* "BC": Backwards compatibility break (from public component APIs) +* "BF": Bug fix + +--- + +* 0.4.1 (2017-12-11) + * Only enableKeepAlive in App if no WsServer passed allowing user to set their own timeout duration + * Support Symfony 4 + * BF: Plug NOOP controller in connection from router in case of misbehaving client + * BF: Raise error from invalid WAMP payload + +* 0.4 (2017-09-14) + * BC: $conn->WebSocket->request replaced with $conn->httpRequest which is a PSR-7 object + * Binary messages now supported via Ratchet\WebSocket\MessageComponentInterface + * Added heartbeat support via ping/pong in WsServer + * BC: No longer support old (and insecure) Hixie76 and Hybi protocols + * BC: No longer support disabling UTF-8 checks + * BC: The Session component implements HttpServerInterface instead of WsServerInterface + * BC: PHP 5.3 no longer supported + * BC: Update to newer version of react/socket dependency + * BC: WAMP topics reduced to 0 subscriptions are deleted, new subs to same name will result in new Topic instance + * Significant performance enhancements + +* 0.3.6 (2017-01-06) + * BF: Keep host and scheme in HTTP request object attatched to connection + * BF: Return correct HTTP response (405) when non-GET request made + +* 0.3.5 (2016-05-25) + * BF: Unmask responding close frame + * Added write handler for PHP session serializer + +* 0.3.4 (2015-12-23) + * BF: Edge case where version check wasn't run on message coalesce + * BF: Session didn't start when using pdo_sqlite + * BF: WAMP currie prefix check when using '#' + * Compatibility with Symfony 3 + +* 0.3.3 (2015-05-26) + * BF: Framing bug on large messages upon TCP fragmentation + * BF: Symfony Router query parameter defaults applied to Request + * BF: WAMP CURIE on all URIs + * OriginCheck rules applied to FlashPolicy + * Switched from PSR-0 to PSR-4 + +* 0.3.2 (2014-06-08) + * BF: No messages after closing handshake (fixed rare race condition causing 100% CPU) + * BF: Fixed accidental BC break from v0.3.1 + * Added autoDelete parameter to Topic to destroy when empty of connections + * Exposed React Socket on IoServer (allowing FlashPolicy shutdown in App) + * Normalized Exceptions in WAMP + +* 0.3.1 (2014-05-26) + * Added query parameter support to Router, set in HTTP request (ws://server?hello=world) + * HHVM compatibility + * BF: React/0.4 support; CPU starvation bug fixes + * BF: Allow App::route to ignore Host header + * Added expected filters to WAMP Topic broadcast method + * Resource cleanup in WAMP TopicManager + +* 0.3.0 (2013-10-14) + * Added the `App` class to help making Ratchet so easy to use it's silly + * BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks + * Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router + * BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer + * BF: Single sub-protocol selection to conform with RFC6455 + * BF: Sanity checks on WAMP protocol to prevent errors + +* 0.2.8 (2013-09-19) + * React 0.3 support + +* 0.2.7 (2013-06-09) + * BF: Sub-protocol negotation with Guzzle 3.6 + +* 0.2.6 (2013-06-01) + * Guzzle 3.6 support + +* 0.2.5 (2013-04-01) + * Fixed Hixie-76 handshake bug + +* 0.2.4 (2013-03-09) + * Support for Symfony 2.2 and Guzzle 2.3 + * Minor bug fixes when handling errors + +* 0.2.3 (2012-11-21) + * Bumped dep: Guzzle to v3, React to v0.2.4 + * More tests + +* 0.2.2 (2012-10-20) + * Bumped deps to use React v0.2 + +* 0.2.1 (2012-10-13) + * BF: No more UTF-8 warnings in browsers (no longer sending empty sub-protocol string) + * Documentation corrections + * Using new composer structure + +* 0.2 (2012-09-07) + * Ratchet passes every non-binary-frame test from the Autobahn Testsuite + * Major performance improvements + * BC: Renamed "WampServer" to "ServerProtocol" + * BC: New "WampServer" component passes Topic container objects of subscribed Connections + * Option to turn off UTF-8 checks in order to increase performance + * Switched dependency guzzle/guzzle to guzzle/http (no API changes) + * mbstring no longer required + +* 0.1.5 (2012-07-12) + * BF: Error where service wouldn't run on PHP <= 5.3.8 + * Dependency library updates + +* 0.1.4 (2012-06-17) + * Fixed dozens of failing AB tests + * BF: Proper socket buffer handling + +* 0.1.3 (2012-06-15) + * Major refactor inside WebSocket protocol handling, more loosley coupled + * BF: Proper error handling on failed WebSocket connections + * BF: Handle TCP message concatenation + * Inclusion of the AutobahnTestSuite checking WebSocket protocol compliance + * mb_string now a requirement + +* 0.1.2 (2012-05-19) + * BC/BF: Updated WAMP API to coincide with the official spec + * Tweaks to improve running as a long lived process + +* 0.1.1 (2012-05-14) + * Separated interfaces allowing WebSockets to support multiple sub protocols + * BF: remoteAddress variable on connections returns proper value + +* 0.1 (2012-05-11) + * First release with components: IoServer, WsServer, SessionProvider, WampServer, FlashPolicy, IpBlackList + * I/O now handled by React, making Ratchet fully asynchronous diff --git a/assets/php/vendor/cboden/ratchet/LICENSE b/assets/php/vendor/cboden/ratchet/LICENSE new file mode 100644 index 0000000..24abba1 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2017 Chris Boden + +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/assets/php/vendor/cboden/ratchet/Makefile b/assets/php/vendor/cboden/ratchet/Makefile new file mode 100644 index 0000000..a2526c0 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/Makefile @@ -0,0 +1,42 @@ +# This file is intended to ease the author's development and testing process +# Users do not need to use `make`; Ratchet does not need to be compiled + +test: + phpunit + +cover: + phpunit --coverage-text --coverage-html=reports/coverage + +abtests: + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8001 LibEvent & + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8002 StreamSelect & + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv & + wstest -m testeeserver -w ws://localhost:8000 & + sleep 1 + wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json + killall php wstest + +abtest: + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8000 StreamSelect & + sleep 1 + wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json + killall php + +profile: + php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver.php 8000 LibEvent & + sleep 1 + wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json + killall php + +apidocs: + apigen --title Ratchet -d reports/api \ + -s src/ \ + -s vendor/ratchet/rfc6455/src \ + -s vendor/react/event-loop/src \ + -s vendor/react/socket/src \ + -s vendor/react/stream/src \ + -s vendor/psr/http-message/src \ + -s vendor/symfony/http-foundation/Session \ + -s vendor/symfony/routing \ + -s vendor/evenement/evenement/src/Evenement \ + --exclude=vendor/symfony/routing/Tests \ diff --git a/assets/php/vendor/cboden/ratchet/README.md b/assets/php/vendor/cboden/ratchet/README.md new file mode 100644 index 0000000..3f1a345 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/README.md @@ -0,0 +1,83 @@ +# Ratchet + +[![Build Status](https://secure.travis-ci.org/ratchetphp/Ratchet.png?branch=master)](http://travis-ci.org/ratchetphp/Ratchet) +[![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg)](http://socketo.me/reports/ab/index.html) +[![Latest Stable Version](https://poser.pugx.org/cboden/ratchet/v/stable.png)](https://packagist.org/packages/cboden/ratchet) + +A PHP library for asynchronously serving WebSockets. +Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components. + +## Requirements + +Shell access is required and root access is recommended. +To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80 or 443 (SSL), which requires root access. +In order to do this, along with your sync web stack, you can either use a reverse proxy or two separate machines. +You can find more details in the [server conf docs](http://socketo.me/docs/deploy#serverconfiguration). + +### Documentation + +User and API documentation is available on Ratchet's website: http://socketo.me + +See https://github.com/cboden/Ratchet-examples for some out-of-the-box working demos using Ratchet. + +Need help? Have a question? Want to provide feedback? Write a message on the [Google Groups Mailing List](https://groups.google.com/forum/#!forum/ratchet-php). + +--- + +### A quick example + +```php +clients = new \SplObjectStorage; + } + + public function onOpen(ConnectionInterface $conn) { + $this->clients->attach($conn); + } + + public function onMessage(ConnectionInterface $from, $msg) { + foreach ($this->clients as $client) { + if ($from != $client) { + $client->send($msg); + } + } + } + + public function onClose(ConnectionInterface $conn) { + $this->clients->detach($conn); + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + $conn->close(); + } +} + + // Run the server application through the WebSocket protocol on port 8080 + $app = new Ratchet\App('localhost', 8080); + $app->route('/chat', new MyChat); + $app->route('/echo', new Ratchet\Server\EchoServer, array('*')); + $app->run(); +``` + + $ php chat.php + +```javascript + // Then some JavaScript in the browser: + var conn = new WebSocket('ws://localhost:8080/echo'); + conn.onmessage = function(e) { console.log(e.data); }; + conn.onopen = function(e) { conn.send('Hello Me!'); }; +``` diff --git a/assets/php/vendor/cboden/ratchet/composer.json b/assets/php/vendor/cboden/ratchet/composer.json new file mode 100644 index 0000000..9529618 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/composer.json @@ -0,0 +1,36 @@ +{ + "name": "cboden/ratchet" + , "type": "library" + , "description": "PHP WebSocket library" + , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets", "WebSocket"] + , "homepage": "http://socketo.me" + , "license": "MIT" + , "authors": [ + { + "name": "Chris Boden" + , "email": "cboden@gmail.com" + , "role": "Developer" + } + ] + , "support": { + "forum": "https://groups.google.com/forum/#!forum/ratchet-php" + , "issues": "https://github.com/ratchetphp/Ratchet/issues" + , "irc": "irc://irc.freenode.org/reactphp" + } + , "autoload": { + "psr-4": { + "Ratchet\\": "src/Ratchet" + } + } + , "require": { + "php": ">=5.4.2" + , "ratchet/rfc6455": "^0.2" + , "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5" + , "guzzlehttp/psr7": "^1.0" + , "symfony/http-foundation": "^2.6|^3.0|^4.0" + , "symfony/routing": "^2.6|^3.0|^4.0" + } + , "require-dev": { + "phpunit/phpunit": "~4.8" + } +} diff --git a/assets/php/vendor/cboden/ratchet/phpunit.xml.dist b/assets/php/vendor/cboden/ratchet/phpunit.xml.dist new file mode 100644 index 0000000..0cc5451 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./tests/unit/ + + + + + + ./tests/integration/ + + + + + + ./src/ + + + \ No newline at end of file diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/AbstractConnectionDecorator.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/AbstractConnectionDecorator.php new file mode 100644 index 0000000..9707951 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/AbstractConnectionDecorator.php @@ -0,0 +1,41 @@ +wrappedConn = $conn; + } + + /** + * @return ConnectionInterface + */ + protected function getConnection() { + return $this->wrappedConn; + } + + public function __set($name, $value) { + $this->wrappedConn->$name = $value; + } + + public function __get($name) { + return $this->wrappedConn->$name; + } + + public function __isset($name) { + return isset($this->wrappedConn->$name); + } + + public function __unset($name) { + unset($this->wrappedConn->$name); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/App.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/App.php new file mode 100644 index 0000000..f378534 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/App.php @@ -0,0 +1,145 @@ +httpHost = $httpHost; + $this->port = $port; + + $socket = new Reactor($address . ':' . $port, $loop); + + $this->routes = new RouteCollection; + $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop); + + $policy = new FlashPolicy; + $policy->addAllowedAccess($httpHost, 80); + $policy->addAllowedAccess($httpHost, $port); + + if (80 == $port) { + $flashUri = '0.0.0.0:843'; + } else { + $flashUri = 8843; + } + $flashSock = new Reactor($flashUri, $loop); + $this->flashServer = new IoServer($policy, $flashSock); + } + + /** + * Add an endpoint/application to the server + * @param string $path The URI the client will connect to + * @param ComponentInterface $controller Your application to server for the route. If not specified, assumed to be for a WebSocket + * @param array $allowedOrigins An array of hosts allowed to connect (same host by default), ['*'] for any + * @param string $httpHost Override the $httpHost variable provided in the __construct + * @return ComponentInterface|WsServer + */ + public function route($path, ComponentInterface $controller, array $allowedOrigins = array(), $httpHost = null) { + if ($controller instanceof HttpServerInterface || $controller instanceof WsServer) { + $decorated = $controller; + } elseif ($controller instanceof WampServerInterface) { + $decorated = new WsServer(new WampServer($controller)); + $decorated->enableKeepAlive($this->_server->loop); + } elseif ($controller instanceof MessageComponentInterface) { + $decorated = new WsServer($controller); + $decorated->enableKeepAlive($this->_server->loop); + } else { + $decorated = $controller; + } + + if ($httpHost === null) { + $httpHost = $this->httpHost; + } + + $allowedOrigins = array_values($allowedOrigins); + if (0 === count($allowedOrigins)) { + $allowedOrigins[] = $httpHost; + } + if ('*' !== $allowedOrigins[0]) { + $decorated = new OriginCheck($decorated, $allowedOrigins); + } + + //allow origins in flash policy server + if(empty($this->flashServer) === false) { + foreach($allowedOrigins as $allowedOrgin) { + $this->flashServer->app->addAllowedAccess($allowedOrgin, $this->port); + } + } + + $this->routes->add('rr-' . ++$this->_routeCounter, new Route($path, array('_controller' => $decorated), array('Origin' => $this->httpHost), array(), $httpHost, array(), array('GET'))); + + return $decorated; + } + + /** + * Run the server by entering the event loop + */ + public function run() { + $this->_server->run(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/ComponentInterface.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/ComponentInterface.php new file mode 100644 index 0000000..37e41b1 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/ComponentInterface.php @@ -0,0 +1,31 @@ + \Ratchet\VERSION + ], $additional_headers)); + + $conn->send(gPsr\str($response)); + $conn->close(); + } +} \ No newline at end of file diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php new file mode 100644 index 0000000..9c44114 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php @@ -0,0 +1,64 @@ +httpBuffer)) { + $context->httpBuffer = ''; + } + + $context->httpBuffer .= $data; + + if (strlen($context->httpBuffer) > (int)$this->maxSize) { + throw new \OverflowException("Maximum buffer size of {$this->maxSize} exceeded parsing HTTP header"); + } + + if ($this->isEom($context->httpBuffer)) { + $request = $this->parse($context->httpBuffer); + + unset($context->httpBuffer); + + return $request; + } + } + + /** + * Determine if the message has been buffered as per the HTTP specification + * @param string $message + * @return boolean + */ + public function isEom($message) { + return (boolean)strpos($message, static::EOM); + } + + /** + * @param string $headers + * @return \Psr\Http\Message\RequestInterface + */ + public function parse($headers) { + return gPsr\parse_request($headers); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServer.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServer.php new file mode 100644 index 0000000..bbd8d53 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServer.php @@ -0,0 +1,76 @@ +_httpServer = $component; + $this->_reqParser = new HttpRequestParser; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn) { + $conn->httpHeadersReceived = false; + } + + /** + * {@inheritdoc} + */ + public function onMessage(ConnectionInterface $from, $msg) { + if (true !== $from->httpHeadersReceived) { + try { + if (null === ($request = $this->_reqParser->onMessage($from, $msg))) { + return; + } + } catch (\OverflowException $oe) { + return $this->close($from, 413); + } + + $from->httpHeadersReceived = true; + + return $this->_httpServer->onOpen($from, $request); + } + + $this->_httpServer->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + if ($conn->httpHeadersReceived) { + $this->_httpServer->onClose($conn); + } + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + if ($conn->httpHeadersReceived) { + $this->_httpServer->onError($conn, $e); + } else { + $this->close($conn, 500); + } + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServerInterface.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServerInterface.php new file mode 100644 index 0000000..2c37c49 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/HttpServerInterface.php @@ -0,0 +1,14 @@ +_component = $component; + $this->allowedOrigins += $allowed; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + $header = (string)$request->getHeader('Origin')[0]; + $origin = parse_url($header, PHP_URL_HOST) ?: $header; + + if (!in_array($origin, $this->allowedOrigins)) { + return $this->close($conn, 403); + } + + return $this->_component->onOpen($conn, $request); + } + + /** + * {@inheritdoc} + */ + function onMessage(ConnectionInterface $from, $msg) { + return $this->_component->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + function onClose(ConnectionInterface $conn) { + return $this->_component->onClose($conn); + } + + /** + * {@inheritdoc} + */ + function onError(ConnectionInterface $conn, \Exception $e) { + return $this->_component->onError($conn, $e); + } +} \ No newline at end of file diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/Router.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/Router.php new file mode 100644 index 0000000..df7fe82 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Http/Router.php @@ -0,0 +1,96 @@ +_matcher = $matcher; + $this->_noopController = new NoOpHttpServerController; + } + + /** + * {@inheritdoc} + * @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface + */ + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + if (null === $request) { + throw new \UnexpectedValueException('$request can not be null'); + } + + $conn->controller = $this->_noopController; + + $uri = $request->getUri(); + + $context = $this->_matcher->getContext(); + $context->setMethod($request->getMethod()); + $context->setHost($uri->getHost()); + + try { + $route = $this->_matcher->match($uri->getPath()); + } catch (MethodNotAllowedException $nae) { + return $this->close($conn, 405, array('Allow' => $nae->getAllowedMethods())); + } catch (ResourceNotFoundException $nfe) { + return $this->close($conn, 404); + } + + if (is_string($route['_controller']) && class_exists($route['_controller'])) { + $route['_controller'] = new $route['_controller']; + } + + if (!($route['_controller'] instanceof HttpServerInterface)) { + throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface'); + } + + $parameters = []; + foreach($route as $key => $value) { + if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { + $parameters[$key] = $value; + } + } + $parameters = array_merge($parameters, gPsr\parse_query($uri->getQuery() ?: '')); + + $request = $request->withUri($uri->withQuery(gPsr\build_query($parameters))); + + $conn->controller = $route['_controller']; + $conn->controller->onOpen($conn, $request); + } + + /** + * {@inheritdoc} + */ + public function onMessage(ConnectionInterface $from, $msg) { + $from->controller->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + if (isset($conn->controller)) { + $conn->controller->onClose($conn); + } + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + if (isset($conn->controller)) { + $conn->controller->onError($conn, $e); + } + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/MessageComponentInterface.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/MessageComponentInterface.php new file mode 100644 index 0000000..b4a92e2 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/MessageComponentInterface.php @@ -0,0 +1,5 @@ +send($msg); + } + + public function onClose(ConnectionInterface $conn) { + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + $conn->close(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/FlashPolicy.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/FlashPolicy.php new file mode 100644 index 0000000..4a1b8bd --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/FlashPolicy.php @@ -0,0 +1,200 @@ +'; + + /** + * Stores an array of allowed domains and their ports + * @var array + */ + protected $_access = array(); + + /** + * @var string + */ + protected $_siteControl = ''; + + /** + * @var string + */ + protected $_cache = ''; + + /** + * @var string + */ + protected $_cacheValid = false; + + /** + * Add a domain to an allowed access list. + * + * @param string $domain Specifies a requesting domain to be granted access. Both named domains and IP + * addresses are acceptable values. Subdomains are considered different domains. A wildcard (*) can + * be used to match all domains when used alone, or multiple domains (subdomains) when used as a + * prefix for an explicit, second-level domain name separated with a dot (.) + * @param string $ports A comma-separated list of ports or range of ports that a socket connection + * is allowed to connect to. A range of ports is specified through a dash (-) between two port numbers. + * Ranges can be used with individual ports when separated with a comma. A single wildcard (*) can + * be used to allow all ports. + * @param bool $secure + * @throws \UnexpectedValueException + * @return FlashPolicy + */ + public function addAllowedAccess($domain, $ports = '*', $secure = false) { + if (!$this->validateDomain($domain)) { + throw new \UnexpectedValueException('Invalid domain'); + } + + if (!$this->validatePorts($ports)) { + throw new \UnexpectedValueException('Invalid Port'); + } + + $this->_access[] = array($domain, $ports, (boolean)$secure); + $this->_cacheValid = false; + + return $this; + } + + /** + * Removes all domains from the allowed access list. + * + * @return \Ratchet\Server\FlashPolicy + */ + public function clearAllowedAccess() { + $this->_access = array(); + $this->_cacheValid = false; + + return $this; + } + + /** + * site-control defines the meta-policy for the current domain. A meta-policy specifies acceptable + * domain policy files other than the master policy file located in the target domain's root and named + * crossdomain.xml. + * + * @param string $permittedCrossDomainPolicies + * @throws \UnexpectedValueException + * @return FlashPolicy + */ + public function setSiteControl($permittedCrossDomainPolicies = 'all') { + if (!$this->validateSiteControl($permittedCrossDomainPolicies)) { + throw new \UnexpectedValueException('Invalid site control set'); + } + + $this->_siteControl = $permittedCrossDomainPolicies; + $this->_cacheValid = false; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn) { + } + + /** + * {@inheritdoc} + */ + public function onMessage(ConnectionInterface $from, $msg) { + if (!$this->_cacheValid) { + $this->_cache = $this->renderPolicy()->asXML(); + $this->_cacheValid = true; + } + + $from->send($this->_cache . "\0"); + $from->close(); + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + $conn->close(); + } + + /** + * Builds the crossdomain file based on the template policy + * + * @throws \UnexpectedValueException + * @return \SimpleXMLElement + */ + public function renderPolicy() { + $policy = new \SimpleXMLElement($this->_policy); + + $siteControl = $policy->addChild('site-control'); + + if ($this->_siteControl == '') { + $this->setSiteControl(); + } + + $siteControl->addAttribute('permitted-cross-domain-policies', $this->_siteControl); + + if (empty($this->_access)) { + throw new \UnexpectedValueException('You must add a domain through addAllowedAccess()'); + } + + foreach ($this->_access as $access) { + $tmp = $policy->addChild('allow-access-from'); + $tmp->addAttribute('domain', $access[0]); + $tmp->addAttribute('to-ports', $access[1]); + $tmp->addAttribute('secure', ($access[2] === true) ? 'true' : 'false'); + } + + return $policy; + } + + /** + * Make sure the proper site control was passed + * + * @param string $permittedCrossDomainPolicies + * @return bool + */ + public function validateSiteControl($permittedCrossDomainPolicies) { + //'by-content-type' and 'by-ftp-filename' are not available for sockets + return (bool)in_array($permittedCrossDomainPolicies, array('none', 'master-only', 'all')); + } + + /** + * Validate for proper domains (wildcards allowed) + * + * @param string $domain + * @return bool + */ + public function validateDomain($domain) { + return (bool)preg_match("/^((http(s)?:\/\/)?([a-z0-9-_]+\.|\*\.)*([a-z0-9-_\.]+)|\*)$/i", $domain); + } + + /** + * Make sure valid ports were passed + * + * @param string $port + * @return bool + */ + public function validatePorts($port) { + return (bool)preg_match('/^(\*|(\d+[,-]?)*\d+)$/', $port); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoConnection.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoConnection.php new file mode 100644 index 0000000..9f864bb --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoConnection.php @@ -0,0 +1,38 @@ +conn = $conn; + } + + /** + * {@inheritdoc} + */ + public function send($data) { + $this->conn->write($data); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function close() { + $this->conn->end(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoServer.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoServer.php new file mode 100644 index 0000000..b3fb7e0 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IoServer.php @@ -0,0 +1,140 @@ +loop = $loop; + $this->app = $app; + $this->socket = $socket; + + $socket->on('connection', array($this, 'handleConnect')); + } + + /** + * @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received + * @param int $port The port to server sockets on + * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) + * @return IoServer + */ + public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') { + $loop = LoopFactory::create(); + $socket = new Reactor($address . ':' . $port, $loop); + + return new static($component, $socket, $loop); + } + + /** + * Run the application by entering the event loop + * @throws \RuntimeException If a loop was not previously specified + */ + public function run() { + if (null === $this->loop) { + throw new \RuntimeException("A React Loop was not provided during instantiation"); + } + + // @codeCoverageIgnoreStart + $this->loop->run(); + // @codeCoverageIgnoreEnd + } + + /** + * Triggered when a new connection is received from React + * @param \React\Socket\ConnectionInterface $conn + */ + public function handleConnect($conn) { + $conn->decor = new IoConnection($conn); + $conn->decor->resourceId = (int)$conn->stream; + + $uri = $conn->getRemoteAddress(); + $conn->decor->remoteAddress = trim( + parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_HOST), + '[]' + ); + + $this->app->onOpen($conn->decor); + + $conn->on('data', function ($data) use ($conn) { + $this->handleData($data, $conn); + }); + $conn->on('close', function () use ($conn) { + $this->handleEnd($conn); + }); + $conn->on('error', function (\Exception $e) use ($conn) { + $this->handleError($e, $conn); + }); + } + + /** + * Data has been received from React + * @param string $data + * @param \React\Socket\ConnectionInterface $conn + */ + public function handleData($data, $conn) { + try { + $this->app->onMessage($conn->decor, $data); + } catch (\Exception $e) { + $this->handleError($e, $conn); + } + } + + /** + * A connection has been closed by React + * @param \React\Socket\ConnectionInterface $conn + */ + public function handleEnd($conn) { + try { + $this->app->onClose($conn->decor); + } catch (\Exception $e) { + $this->handleError($e, $conn); + } + + unset($conn->decor); + } + + /** + * An error has occurred, let the listening application know + * @param \Exception $e + * @param \React\Socket\ConnectionInterface $conn + */ + public function handleError(\Exception $e, $conn) { + $this->app->onError($conn->decor, $e); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IpBlackList.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IpBlackList.php new file mode 100644 index 0000000..9342254 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Server/IpBlackList.php @@ -0,0 +1,111 @@ +_decorating = $component; + } + + /** + * Add an address to the blacklist that will not be allowed to connect to your application + * @param string $ip IP address to block from connecting to your application + * @return IpBlackList + */ + public function blockAddress($ip) { + $this->_blacklist[$ip] = true; + + return $this; + } + + /** + * Unblock an address so they can access your application again + * @param string $ip IP address to unblock from connecting to your application + * @return IpBlackList + */ + public function unblockAddress($ip) { + if (isset($this->_blacklist[$this->filterAddress($ip)])) { + unset($this->_blacklist[$this->filterAddress($ip)]); + } + + return $this; + } + + /** + * @param string $address + * @return bool + */ + public function isBlocked($address) { + return (isset($this->_blacklist[$this->filterAddress($address)])); + } + + /** + * Get an array of all the addresses blocked + * @return array + */ + public function getBlockedAddresses() { + return array_keys($this->_blacklist); + } + + /** + * @param string $address + * @return string + */ + public function filterAddress($address) { + if (strstr($address, ':') && substr_count($address, '.') == 3) { + list($address, $port) = explode(':', $address); + } + + return $address; + } + + /** + * {@inheritdoc} + */ + function onOpen(ConnectionInterface $conn) { + if ($this->isBlocked($conn->remoteAddress)) { + return $conn->close(); + } + + return $this->_decorating->onOpen($conn); + } + + /** + * {@inheritdoc} + */ + function onMessage(ConnectionInterface $from, $msg) { + return $this->_decorating->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + function onClose(ConnectionInterface $conn) { + if (!$this->isBlocked($conn->remoteAddress)) { + $this->_decorating->onClose($conn); + } + } + + /** + * {@inheritdoc} + */ + function onError(ConnectionInterface $conn, \Exception $e) { + if (!$this->isBlocked($conn->remoteAddress)) { + $this->_decorating->onError($conn, $e); + } + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/HandlerInterface.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/HandlerInterface.php new file mode 100644 index 0000000..b83635f --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Serialize/HandlerInterface.php @@ -0,0 +1,16 @@ + $bucketData) { + $preSerialized[] = $bucket . '|' . serialize($bucketData); + } + $serialized = implode('', $preSerialized); + } + + return $serialized; + } + + /** + * {@inheritdoc} + * @link http://ca2.php.net/manual/en/function.session-decode.php#108037 Code from this comment on php.net + * @throws \UnexpectedValueException If there is a problem parsing the data + */ + public function unserialize($raw) { + $returnData = array(); + $offset = 0; + + while ($offset < strlen($raw)) { + if (!strstr(substr($raw, $offset), "|")) { + throw new \UnexpectedValueException("invalid data, remaining: " . substr($raw, $offset)); + } + + $pos = strpos($raw, "|", $offset); + $num = $pos - $offset; + $varname = substr($raw, $offset, $num); + $offset += $num + 1; + $data = unserialize(substr($raw, $offset)); + + $returnData[$varname] = $data; + $offset += strlen(serialize($data)); + } + + return $returnData; + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/SessionProvider.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/SessionProvider.php new file mode 100644 index 0000000..44276c5 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/SessionProvider.php @@ -0,0 +1,243 @@ +_app = $app; + $this->_handler = $handler; + $this->_null = new NullSessionHandler; + + ini_set('session.auto_start', 0); + ini_set('session.cache_limiter', ''); + ini_set('session.use_cookies', 0); + + $this->setOptions($options); + + if (null === $serializer) { + $serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase(ini_get('session.serialize_handler'))}Handler"; // awesome/terrible hack, eh? + if (!class_exists($serialClass)) { + throw new \RuntimeException('Unable to parse session serialize handler'); + } + + $serializer = new $serialClass; + } + + $this->_serializer = $serializer; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + $sessionName = ini_get('session.name'); + + $id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) { + if ($accumulator) { + return $accumulator; + } + + $crumbs = $this->parseCookie($cookie); + + return isset($crumbs['cookies'][$sessionName]) ? $crumbs['cookies'][$sessionName] : false; + }, false); + + if (null === $request || false === $id) { + $saveHandler = $this->_null; + $id = ''; + } else { + $saveHandler = $this->_handler; + } + + $conn->Session = new Session(new VirtualSessionStorage($saveHandler, $id, $this->_serializer)); + + if (ini_get('session.auto_start')) { + $conn->Session->start(); + } + + return $this->_app->onOpen($conn, $request); + } + + /** + * {@inheritdoc} + */ + function onMessage(ConnectionInterface $from, $msg) { + return $this->_app->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + function onClose(ConnectionInterface $conn) { + // "close" session for Connection + + return $this->_app->onClose($conn); + } + + /** + * {@inheritdoc} + */ + function onError(ConnectionInterface $conn, \Exception $e) { + return $this->_app->onError($conn, $e); + } + + /** + * Set all the php session. ini options + * © Symfony + * @param array $options + * @return array + */ + protected function setOptions(array $options) { + $all = array( + 'auto_start', 'cache_limiter', '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', 'name', 'referer_check', + 'serialize_handler', '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' + ); + + foreach ($all as $key) { + if (!array_key_exists($key, $options)) { + $options[$key] = ini_get("session.{$key}"); + } else { + ini_set("session.{$key}", $options[$key]); + } + } + + return $options; + } + + /** + * @param string $langDef Input to convert + * @return string + */ + protected function toClassCase($langDef) { + return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef))); + } + + /** + * Taken from Guzzle3 + */ + private static $cookieParts = array( + 'domain' => 'Domain', + 'path' => 'Path', + 'max_age' => 'Max-Age', + 'expires' => 'Expires', + 'version' => 'Version', + 'secure' => 'Secure', + 'port' => 'Port', + 'discard' => 'Discard', + 'comment' => 'Comment', + 'comment_url' => 'Comment-Url', + 'http_only' => 'HttpOnly' + ); + + /** + * Taken from Guzzle3 + */ + private function parseCookie($cookie, $host = null, $path = null, $decode = false) { + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return false; + } + + // Create the default return array + $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array( + 'cookies' => array(), + 'data' => array(), + 'path' => $path ?: '/', + 'http_only' => false, + 'discard' => false, + 'domain' => $host + )); + $foundNonCookies = 0; + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + + if (count($cookieParts) == 1) { + // Can be a single value (e.g. secure, httpOnly) + $value = true; + } else { + // Be sure to strip wrapping quotes + $value = trim($cookieParts[1], " \n\r\t\0\x0B\""); + if ($decode) { + $value = urldecode($value); + } + } + + // Only check for non-cookies when cookies have been found + if (!empty($data['cookies'])) { + foreach (self::$cookieParts as $mapValue => $search) { + if (!strcasecmp($search, $key)) { + $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value; + $foundNonCookies++; + continue 2; + } + } + } + + // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a + // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data. + $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value; + } + + // Calculate the expires date + if (!$data['expires'] && $data['max_age']) { + $data['expires'] = time() + (int) $data['max_age']; + } + + return $data; + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/Proxy/VirtualProxy.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/Proxy/VirtualProxy.php new file mode 100644 index 0000000..b478d03 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/Proxy/VirtualProxy.php @@ -0,0 +1,54 @@ +saveHandlerName = 'user'; + $this->_sessionName = ini_get('session.name'); + } + + /** + * {@inheritdoc} + */ + public function getId() { + return $this->_sessionId; + } + + /** + * {@inheritdoc} + */ + public function setId($id) { + $this->_sessionId = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return $this->_sessionName; + } + + /** + * DO NOT CALL THIS METHOD + * @internal + */ + public function setName($name) { + throw new \RuntimeException("Can not change session name in VirtualProxy"); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/VirtualSessionStorage.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/VirtualSessionStorage.php new file mode 100644 index 0000000..daa10bb --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Session/Storage/VirtualSessionStorage.php @@ -0,0 +1,88 @@ +setSaveHandler($handler); + $this->saveHandler->setId($sessionId); + $this->_serializer = $serializer; + $this->setMetadataBag(null); + } + + /** + * {@inheritdoc} + */ + public function start() { + if ($this->started && !$this->closed) { + return true; + } + + // You have to call Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::open() to use + // pdo_sqlite (and possible pdo_*) as session storage, if you are using a DSN string instead of a \PDO object + // in the constructor. The method arguments are filled with the values, which are also used by the symfony + // framework in this case. This must not be the best choice, but it works. + $this->saveHandler->open(session_save_path(), session_name()); + + $rawData = $this->saveHandler->read($this->saveHandler->getId()); + $sessionData = $this->_serializer->unserialize($rawData); + + $this->loadSession($sessionData); + + if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { + $this->saveHandler->setActive(false); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) { + // .. ? + } + + /** + * {@inheritdoc} + */ + public function save() { + // get the data from the bags? + // serialize the data + // save the data using the saveHandler +// $this->saveHandler->write($this->saveHandler->getId(), + + if (!$this->saveHandler->isWrapper() && !$this->getSaveHandler()->isSessionHandlerInterface()) { + $this->saveHandler->setActive(false); + } + + $this->closed = true; + } + + /** + * {@inheritdoc} + */ + public function setSaveHandler($saveHandler = null) { + if (!($saveHandler instanceof \SessionHandlerInterface)) { + throw new \InvalidArgumentException('Handler must be instance of SessionHandlerInterface'); + } + + if (!($saveHandler instanceof VirtualProxy)) { + $saveHandler = new VirtualProxy($saveHandler); + } + + $this->saveHandler = $saveHandler; + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Exception.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Exception.php new file mode 100644 index 0000000..6c824da --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Exception.php @@ -0,0 +1,5 @@ +_decorating = $serverComponent; + $this->connections = new \SplObjectStorage; + } + + /** + * {@inheritdoc} + */ + public function getSubProtocols() { + if ($this->_decorating instanceof WsServerInterface) { + $subs = $this->_decorating->getSubProtocols(); + $subs[] = 'wamp'; + + return $subs; + } + + return ['wamp']; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn) { + $decor = new WampConnection($conn); + $this->connections->attach($conn, $decor); + + $this->_decorating->onOpen($decor); + } + + /** + * {@inheritdoc} + * @throws \Ratchet\Wamp\Exception + * @throws \Ratchet\Wamp\JsonException + */ + public function onMessage(ConnectionInterface $from, $msg) { + $from = $this->connections[$from]; + + if (null === ($json = @json_decode($msg, true))) { + throw new JsonException; + } + + if (!is_array($json) || $json !== array_values($json)) { + throw new Exception("Invalid WAMP message format"); + } + + if (isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))) { + throw new Exception('Invalid Topic, must be a string'); + } + + switch ($json[0]) { + case static::MSG_PREFIX: + $from->WAMP->prefixes[$json[1]] = $json[2]; + break; + + case static::MSG_CALL: + array_shift($json); + $callID = array_shift($json); + $procURI = array_shift($json); + + if (count($json) == 1 && is_array($json[0])) { + $json = $json[0]; + } + + $this->_decorating->onCall($from, $callID, $from->getUri($procURI), $json); + break; + + case static::MSG_SUBSCRIBE: + $this->_decorating->onSubscribe($from, $from->getUri($json[1])); + break; + + case static::MSG_UNSUBSCRIBE: + $this->_decorating->onUnSubscribe($from, $from->getUri($json[1])); + break; + + case static::MSG_PUBLISH: + $exclude = (array_key_exists(3, $json) ? $json[3] : null); + if (!is_array($exclude)) { + if (true === (boolean)$exclude) { + $exclude = [$from->WAMP->sessionId]; + } else { + $exclude = []; + } + } + + $eligible = (array_key_exists(4, $json) ? $json[4] : []); + + $this->_decorating->onPublish($from, $from->getUri($json[1]), $json[2], $exclude, $eligible); + break; + + default: + throw new Exception('Invalid WAMP message type'); + } + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + $decor = $this->connections[$conn]; + $this->connections->detach($conn); + + $this->_decorating->onClose($decor); + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + return $this->_decorating->onError($this->connections[$conn], $e); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Topic.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Topic.php new file mode 100644 index 0000000..bca8f67 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/Topic.php @@ -0,0 +1,99 @@ +id = $topicId; + $this->subscribers = new \SplObjectStorage; + } + + /** + * @return string + */ + public function getId() { + return $this->id; + } + + public function __toString() { + return $this->getId(); + } + + /** + * Send a message to all the connections in this topic + * @param string|array $msg Payload to publish + * @param array $exclude A list of session IDs the message should be excluded from (blacklist) + * @param array $eligible A list of session Ids the message should be send to (whitelist) + * @return Topic The same Topic object to chain + */ + public function broadcast($msg, array $exclude = array(), array $eligible = array()) { + $useEligible = (bool)count($eligible); + foreach ($this->subscribers as $client) { + if (in_array($client->WAMP->sessionId, $exclude)) { + continue; + } + + if ($useEligible && !in_array($client->WAMP->sessionId, $eligible)) { + continue; + } + + $client->event($this->id, $msg); + } + + return $this; + } + + /** + * @param WampConnection $conn + * @return boolean + */ + public function has(ConnectionInterface $conn) { + return $this->subscribers->contains($conn); + } + + /** + * @param WampConnection $conn + * @return Topic + */ + public function add(ConnectionInterface $conn) { + $this->subscribers->attach($conn); + + return $this; + } + + /** + * @param WampConnection $conn + * @return Topic + */ + public function remove(ConnectionInterface $conn) { + if ($this->subscribers->contains($conn)) { + $this->subscribers->detach($conn); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + return $this->subscribers; + } + + /** + * {@inheritdoc} + */ + public function count() { + return $this->subscribers->count(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/TopicManager.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/TopicManager.php new file mode 100644 index 0000000..dd06ada --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/TopicManager.php @@ -0,0 +1,125 @@ +app = $app; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn) { + $conn->WAMP->subscriptions = new \SplObjectStorage; + $this->app->onOpen($conn); + } + + /** + * {@inheritdoc} + */ + public function onCall(ConnectionInterface $conn, $id, $topic, array $params) { + $this->app->onCall($conn, $id, $this->getTopic($topic), $params); + } + + /** + * {@inheritdoc} + */ + public function onSubscribe(ConnectionInterface $conn, $topic) { + $topicObj = $this->getTopic($topic); + + if ($conn->WAMP->subscriptions->contains($topicObj)) { + return; + } + + $this->topicLookup[$topic]->add($conn); + $conn->WAMP->subscriptions->attach($topicObj); + $this->app->onSubscribe($conn, $topicObj); + } + + /** + * {@inheritdoc} + */ + public function onUnsubscribe(ConnectionInterface $conn, $topic) { + $topicObj = $this->getTopic($topic); + + if (!$conn->WAMP->subscriptions->contains($topicObj)) { + return; + } + + $this->cleanTopic($topicObj, $conn); + + $this->app->onUnsubscribe($conn, $topicObj); + } + + /** + * {@inheritdoc} + */ + public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) { + $this->app->onPublish($conn, $this->getTopic($topic), $event, $exclude, $eligible); + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + $this->app->onClose($conn); + + foreach ($this->topicLookup as $topic) { + $this->cleanTopic($topic, $conn); + } + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + $this->app->onError($conn, $e); + } + + /** + * {@inheritdoc} + */ + public function getSubProtocols() { + if ($this->app instanceof WsServerInterface) { + return $this->app->getSubProtocols(); + } + + return array(); + } + + /** + * @param string + * @return Topic + */ + protected function getTopic($topic) { + if (!array_key_exists($topic, $this->topicLookup)) { + $this->topicLookup[$topic] = new Topic($topic); + } + + return $this->topicLookup[$topic]; + } + + protected function cleanTopic(Topic $topic, ConnectionInterface $conn) { + if ($conn->WAMP->subscriptions->contains($topic)) { + $conn->WAMP->subscriptions->detach($topic); + } + + $this->topicLookup[$topic->getId()]->remove($conn); + + if (0 === $topic->count()) { + unset($this->topicLookup[$topic->getId()]); + } + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampConnection.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampConnection.php new file mode 100644 index 0000000..dda1e4e --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampConnection.php @@ -0,0 +1,115 @@ +WAMP = new \StdClass; + $this->WAMP->sessionId = str_replace('.', '', uniqid(mt_rand(), true)); + $this->WAMP->prefixes = array(); + + $this->send(json_encode(array(WAMP::MSG_WELCOME, $this->WAMP->sessionId, 1, \Ratchet\VERSION))); + } + + /** + * Successfully respond to a call made by the client + * @param string $id The unique ID given by the client to respond to + * @param array $data an object or array + * @return WampConnection + */ + public function callResult($id, $data = array()) { + return $this->send(json_encode(array(WAMP::MSG_CALL_RESULT, $id, $data))); + } + + /** + * Respond with an error to a client call + * @param string $id The unique ID given by the client to respond to + * @param string $errorUri The URI given to identify the specific error + * @param string $desc A developer-oriented description of the error + * @param string $details An optional human readable detail message to send back + * @return WampConnection + */ + public function callError($id, $errorUri, $desc = '', $details = null) { + if ($errorUri instanceof Topic) { + $errorUri = (string)$errorUri; + } + + $data = array(WAMP::MSG_CALL_ERROR, $id, $errorUri, $desc); + + if (null !== $details) { + $data[] = $details; + } + + return $this->send(json_encode($data)); + } + + /** + * @param string $topic The topic to broadcast to + * @param mixed $msg Data to send with the event. Anything that is json'able + * @return WampConnection + */ + public function event($topic, $msg) { + return $this->send(json_encode(array(WAMP::MSG_EVENT, (string)$topic, $msg))); + } + + /** + * @param string $curie + * @param string $uri + * @return WampConnection + */ + public function prefix($curie, $uri) { + $this->WAMP->prefixes[$curie] = (string)$uri; + + return $this->send(json_encode(array(WAMP::MSG_PREFIX, $curie, (string)$uri))); + } + + /** + * Get the full request URI from the connection object if a prefix has been established for it + * @param string $uri + * @return string + */ + public function getUri($uri) { + $curieSeperator = ':'; + + if (preg_match('/http(s*)\:\/\//', $uri) == false) { + if (strpos($uri, $curieSeperator) !== false) { + list($prefix, $action) = explode($curieSeperator, $uri); + + if(isset($this->WAMP->prefixes[$prefix]) === true){ + return $this->WAMP->prefixes[$prefix] . '#' . $action; + } + } + } + + return $uri; + } + + /** + * @internal + */ + public function send($data) { + $this->getConnection()->send($data); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function close($opt = null) { + $this->getConnection()->close($opt); + } + +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServer.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServer.php new file mode 100644 index 0000000..5d710aa --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServer.php @@ -0,0 +1,67 @@ +wampProtocol = new ServerProtocol(new TopicManager($app)); + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn) { + $this->wampProtocol->onOpen($conn); + } + + /** + * {@inheritdoc} + */ + public function onMessage(ConnectionInterface $conn, $msg) { + try { + $this->wampProtocol->onMessage($conn, $msg); + } catch (Exception $we) { + $conn->close(1007); + } + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + $this->wampProtocol->onClose($conn); + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + $this->wampProtocol->onError($conn, $e); + } + + /** + * {@inheritdoc} + */ + public function getSubProtocols() { + return $this->wampProtocol->getSubProtocols(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServerInterface.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServerInterface.php new file mode 100644 index 0000000..15c521d --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/Wamp/WampServerInterface.php @@ -0,0 +1,43 @@ +connection = $conn; + $this->buffer = $buffer; + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageCallableInterface.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageCallableInterface.php new file mode 100644 index 0000000..b5c094e --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/MessageCallableInterface.php @@ -0,0 +1,8 @@ +WebSocket->closing) { + if (!($msg instanceof DataInterface)) { + $msg = new Frame($msg); + } + + $this->getConnection()->send($msg->getContents()); + } + + return $this; + } + + /** + * @param int|\Ratchet\RFC6455\Messaging\DataInterface + */ + public function close($code = 1000) { + if ($this->WebSocket->closing) { + return; + } + + if ($code instanceof DataInterface) { + $this->send($code); + } else { + $this->send(new Frame(pack('n', $code), true, Frame::OP_CLOSE)); + } + + $this->getConnection()->close(); + + $this->WebSocket->closing = true; + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServer.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServer.php new file mode 100644 index 0000000..8030604 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServer.php @@ -0,0 +1,225 @@ +msgCb = function(ConnectionInterface $conn, MessageInterface $msg) { + $this->delegate->onMessage($conn, $msg); + }; + } elseif ($component instanceof DataComponentInterface) { + $this->msgCb = function(ConnectionInterface $conn, MessageInterface $msg) { + $this->delegate->onMessage($conn, $msg->getPayload()); + }; + } else { + throw new \UnexpectedValueException('Expected instance of \Ratchet\WebSocket\MessageComponentInterface or \Ratchet\MessageComponentInterface'); + } + + if (bin2hex('✓') !== 'e29c93') { + throw new \DomainException('Bad encoding, unicode character ✓ did not match expected value. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); + } + + $this->delegate = $component; + $this->connections = new \SplObjectStorage; + + $this->closeFrameChecker = new CloseFrameChecker; + $this->handshakeNegotiator = new ServerNegotiator(new RequestVerifier); + $this->handshakeNegotiator->setStrictSubProtocolCheck(true); + + if ($component instanceof WsServerInterface) { + $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols()); + } + + $this->pongReceiver = function() {}; + + $reusableUnderflowException = new \UnderflowException; + $this->ueFlowFactory = function() use ($reusableUnderflowException) { + return $reusableUnderflowException; + }; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + if (null === $request) { + throw new \UnexpectedValueException('$request can not be null'); + } + + $conn->httpRequest = $request; + + $conn->WebSocket = new \StdClass; + $conn->WebSocket->closing = false; + + $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION); + + $conn->send(gPsr\str($response)); + + if (101 !== $response->getStatusCode()) { + return $conn->close(); + } + + $wsConn = new WsConnection($conn); + + $streamer = new MessageBuffer( + $this->closeFrameChecker, + function(MessageInterface $msg) use ($wsConn) { + $cb = $this->msgCb; + $cb($wsConn, $msg); + }, + function(FrameInterface $frame) use ($wsConn) { + $this->onControlFrame($frame, $wsConn); + }, + true, + $this->ueFlowFactory + ); + + $this->connections->attach($conn, new ConnContext($wsConn, $streamer)); + + return $this->delegate->onOpen($wsConn); + } + + /** + * {@inheritdoc} + */ + public function onMessage(ConnectionInterface $from, $msg) { + if ($from->WebSocket->closing) { + return; + } + + $this->connections[$from]->buffer->onData($msg); + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + if ($this->connections->contains($conn)) { + $context = $this->connections[$conn]; + $this->connections->detach($conn); + + $this->delegate->onClose($context->connection); + } + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + if ($this->connections->contains($conn)) { + $this->delegate->onError($this->connections[$conn]->connection, $e); + } else { + $conn->close(); + } + } + + public function onControlFrame(FrameInterface $frame, WsConnection $conn) { + switch ($frame->getOpCode()) { + case Frame::OP_CLOSE: + $conn->close($frame); + break; + case Frame::OP_PING: + $conn->send(new Frame($frame->getPayload(), true, Frame::OP_PONG)); + break; + case Frame::OP_PONG: + $pongReceiver = $this->pongReceiver; + $pongReceiver($frame, $conn); + break; + } + } + + public function setStrictSubProtocolCheck($enable) { + $this->handshakeNegotiator->setStrictSubProtocolCheck($enable); + } + + public function enableKeepAlive(LoopInterface $loop, $interval = 30) { + $lastPing = new Frame(uniqid(), true, Frame::OP_PING); + $pingedConnections = new \SplObjectStorage; + $splClearer = new \SplObjectStorage; + + $this->pongReceiver = function(FrameInterface $frame, $wsConn) use ($pingedConnections, &$lastPing) { + if ($frame->getPayload() === $lastPing->getPayload()) { + $pingedConnections->detach($wsConn); + } + }; + + $loop->addPeriodicTimer((int)$interval, function() use ($pingedConnections, &$lastPing, $splClearer) { + foreach ($pingedConnections as $wsConn) { + $wsConn->close(); + } + $pingedConnections->removeAllExcept($splClearer); + + $lastPing = new Frame(uniqid(), true, Frame::OP_PING); + + foreach ($this->connections as $key => $conn) { + $wsConn = $this->connections[$conn]->connection; + + $wsConn->send($lastPing); + $pingedConnections->attach($wsConn); + } + }); + } +} diff --git a/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServerInterface.php b/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServerInterface.php new file mode 100644 index 0000000..15d1f7b --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/src/Ratchet/WebSocket/WsServerInterface.php @@ -0,0 +1,14 @@ +send($msg); + } + + public function onOpen(ConnectionInterface $conn) { + } + + public function onClose(ConnectionInterface $conn) { + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + } +} + + $port = $argc > 1 ? $argv[1] : 8000; + $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); + + $loop = new $impl; + $sock = new React\Socket\Server('0.0.0.0:' . $port, $loop); + + $wsServer = new Ratchet\WebSocket\WsServer(new BinaryEcho); + // This is enabled to test https://github.com/ratchetphp/Ratchet/issues/430 + // The time is left at 10 minutes so that it will not try to every ping anything + // This causes the Ratchet server to crash on test 2.7 + $wsServer->enableKeepAlive($loop, 600); + + $app = new Ratchet\Http\HttpServer($wsServer); + + $server = new Ratchet\Server\IoServer($app, $sock, $loop); + $server->run(); diff --git a/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-all.json b/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-all.json new file mode 100644 index 0000000..0494cf3 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-all.json @@ -0,0 +1,15 @@ +{ + "options": {"failByDrop": false} + , "outdir": "reports/ab" + + , "servers": [ + {"agent": "Ratchet/0.4 libevent", "url": "ws://localhost:8001", "options": {"version": 18}} + , {"agent": "Ratchet/0.4 libev", "url": "ws://localhost:8004", "options": {"version": 18}} + , {"agent": "Ratchet/0.4 streams", "url": "ws://localhost:8002", "options": {"version": 18}} + , {"agent": "AutobahnTestSuite/0.5.9", "url": "ws://localhost:8000", "options": {"version": 18}} + ] + + , "cases": ["*"] + , "exclude-cases": [] + , "exclude-agent-cases": {} +} diff --git a/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-profile.json b/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-profile.json new file mode 100644 index 0000000..e81a9fd --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-profile.json @@ -0,0 +1,12 @@ +{ + "options": {"failByDrop": false} + , "outdir": "reports/profile" + + , "servers": [ + {"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}} + ] + + , "cases": ["9.7.4"] + , "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] + , "exclude-agent-cases": {} +} diff --git a/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-quick.json b/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-quick.json new file mode 100644 index 0000000..c92e805 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/autobahn/fuzzingclient-quick.json @@ -0,0 +1,12 @@ +{ + "options": {"failByDrop": false} + , "outdir": "reports/rfc" + + , "servers": [ + {"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}} + ] + + , "cases": ["*"] + , "exclude-cases": [] + , "exclude-agent-cases": {} +} diff --git a/assets/php/vendor/cboden/ratchet/tests/bootstrap.php b/assets/php/vendor/cboden/ratchet/tests/bootstrap.php new file mode 100644 index 0000000..40791ba --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/bootstrap.php @@ -0,0 +1,4 @@ +addPsr4('Ratchet\\', __DIR__ . '/helpers/Ratchet'); diff --git a/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php new file mode 100644 index 0000000..8c298e5 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php @@ -0,0 +1,50 @@ +_app = $this->getMock($this->getComponentClassString()); + $decorator = $this->getDecoratorClassString(); + $this->_serv = new $decorator($this->_app); + $this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); + + $this->doOpen($this->_conn); + } + + protected function doOpen($conn) { + $this->_serv->onOpen($conn); + } + + public function isExpectedConnection() { + return new \PHPUnit_Framework_Constraint_IsInstanceOf($this->getConnectionClassString()); + } + + public function testOpen() { + $this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection()); + $this->doOpen($this->getMock('\Ratchet\ConnectionInterface')); + } + + public function testOnClose() { + $this->_app->expects($this->once())->method('onClose')->with($this->isExpectedConnection()); + $this->_serv->onClose($this->_conn); + } + + public function testOnError() { + $e = new \Exception('Whoops!'); + $this->_app->expects($this->once())->method('onError')->with($this->isExpectedConnection(), $e); + $this->_serv->onError($this->_conn, $e); + } + + public function passthroughMessageTest($value) { + $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $value); + $this->_serv->onMessage($this->_conn, $value); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Component.php b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Component.php new file mode 100644 index 0000000..e152988 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Component.php @@ -0,0 +1,35 @@ +last[__FUNCTION__] = func_get_args(); + } + + public function onOpen(ConnectionInterface $conn) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onMessage(ConnectionInterface $from, $msg) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onClose(ConnectionInterface $conn) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function getSubProtocols() { + return $this->protocols; + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Connection.php b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Connection.php new file mode 100644 index 0000000..5918296 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/Connection.php @@ -0,0 +1,20 @@ + '' + , 'close' => false + ); + + public $remoteAddress = '127.0.0.1'; + + public function send($data) { + $this->last[__FUNCTION__] = $data; + } + + public function close() { + $this->last[__FUNCTION__] = true; + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/ConnectionDecorator.php b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/ConnectionDecorator.php new file mode 100644 index 0000000..5570c07 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/ConnectionDecorator.php @@ -0,0 +1,22 @@ + '' + , 'end' => false + ); + + public function send($data) { + $this->last[__FUNCTION__] = $data; + + $this->getConnection()->send($data); + } + + public function close() { + $this->last[__FUNCTION__] = true; + + $this->getConnection()->close(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/WampComponent.php b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/WampComponent.php new file mode 100644 index 0000000..cd526cb --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/Mock/WampComponent.php @@ -0,0 +1,43 @@ +protocols; + } + + public function onCall(ConnectionInterface $conn, $id, $procURI, array $params) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onSubscribe(ConnectionInterface $conn, $topic) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onUnSubscribe(ConnectionInterface $conn, $topic) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onOpen(ConnectionInterface $conn) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onClose(ConnectionInterface $conn) { + $this->last[__FUNCTION__] = func_get_args(); + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + $this->last[__FUNCTION__] = func_get_args(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/NullComponent.php b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/NullComponent.php new file mode 100644 index 0000000..90def21 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/helpers/Ratchet/NullComponent.php @@ -0,0 +1,28 @@ +mock = $this->getMock('\Ratchet\ConnectionInterface'); + $this->l1 = new ConnectionDecorator($this->mock); + $this->l2 = new ConnectionDecorator($this->l1); + } + + public function testGet() { + $var = 'hello'; + $val = 'world'; + + $this->mock->$var = $val; + + $this->assertEquals($val, $this->l1->$var); + $this->assertEquals($val, $this->l2->$var); + } + + public function testSet() { + $var = 'Chris'; + $val = 'Boden'; + + $this->l1->$var = $val; + + $this->assertEquals($val, $this->mock->$var); + } + + public function testSetLevel2() { + $var = 'Try'; + $val = 'Again'; + + $this->l2->$var = $val; + + $this->assertEquals($val, $this->mock->$var); + } + + public function testIsSetTrue() { + $var = 'PHP'; + $val = 'Ratchet'; + + $this->mock->$var = $val; + + $this->assertTrue(isset($this->l1->$var)); + $this->assertTrue(isset($this->l2->$var)); + } + + public function testIsSetFalse() { + $var = 'herp'; + $val = 'derp'; + + $this->assertFalse(isset($this->l1->$var)); + $this->assertFalse(isset($this->l2->$var)); + } + + public function testUnset() { + $var = 'Flying'; + $val = 'Monkey'; + + $this->mock->$var = $val; + unset($this->l1->$var); + + $this->assertFalse(isset($this->mock->$var)); + } + + public function testUnsetLevel2() { + $var = 'Flying'; + $val = 'Monkey'; + + $this->mock->$var = $val; + unset($this->l2->$var); + + $this->assertFalse(isset($this->mock->$var)); + } + + public function testGetConnection() { + $class = new \ReflectionClass('\\Ratchet\\AbstractConnectionDecorator'); + $method = $class->getMethod('getConnection'); + $method->setAccessible(true); + + $conn = $method->invokeArgs($this->l1, array()); + + $this->assertSame($this->mock, $conn); + } + + public function testGetConnectionLevel2() { + $class = new \ReflectionClass('\\Ratchet\\AbstractConnectionDecorator'); + $method = $class->getMethod('getConnection'); + $method->setAccessible(true); + + $conn = $method->invokeArgs($this->l2, array()); + + $this->assertSame($this->l1, $conn); + } + + public function testWrapperCanStoreSelfInDecorator() { + $this->mock->decorator = $this->l1; + + $this->assertSame($this->l1, $this->l2->decorator); + } + + public function testDecoratorRecursion() { + $this->mock->decorator = new \stdClass; + $this->mock->decorator->conn = $this->l1; + + $this->assertSame($this->l1, $this->mock->decorator->conn); + $this->assertSame($this->l1, $this->l1->decorator->conn); + $this->assertSame($this->l1, $this->l2->decorator->conn); + } + + public function testDecoratorRecursionLevel2() { + $this->mock->decorator = new \stdClass; + $this->mock->decorator->conn = $this->l2; + + $this->assertSame($this->l2, $this->mock->decorator->conn); + $this->assertSame($this->l2, $this->l1->decorator->conn); + $this->assertSame($this->l2, $this->l2->decorator->conn); + + // just for fun + $this->assertSame($this->l2, $this->l2->decorator->conn->decorator->conn->decorator->conn); + } + + public function testWarningGettingNothing() { + $this->setExpectedException('PHPUnit_Framework_Error'); + $var = $this->mock->nonExistant; + } + + public function testWarningGettingNothingLevel1() { + $this->setExpectedException('PHPUnit_Framework_Error'); + $var = $this->l1->nonExistant; + } + + public function testWarningGettingNothingLevel2() { + $this->setExpectedException('PHPUnit_Framework_Error'); + $var = $this->l2->nonExistant; + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpRequestParserTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpRequestParserTest.php new file mode 100644 index 0000000..6af8402 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpRequestParserTest.php @@ -0,0 +1,50 @@ +parser = new HttpRequestParser; + } + + public function headersProvider() { + return array( + array(false, "GET / HTTP/1.1\r\nHost: socketo.me\r\n") + , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n") + , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n1") + , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖") + , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖\r\n\r\n") + , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie\r\n") + ); + } + + /** + * @dataProvider headersProvider + */ + public function testIsEom($expected, $message) { + $this->assertEquals($expected, $this->parser->isEom($message)); + } + + public function testBufferOverflowResponse() { + $conn = $this->getMock('\Ratchet\ConnectionInterface'); + + $this->parser->maxSize = 20; + + $this->assertNull($this->parser->onMessage($conn, "GET / HTTP/1.1\r\n")); + + $this->setExpectedException('OverflowException'); + + $this->parser->onMessage($conn, "Header-Is: Too Big"); + } + + public function testReturnTypeIsRequest() { + $conn = $this->getMock('\Ratchet\ConnectionInterface'); + $return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"); + + $this->assertInstanceOf('\Psr\Http\Message\RequestInterface', $return); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpServerTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpServerTest.php new file mode 100644 index 0000000..7041d66 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Http/HttpServerTest.php @@ -0,0 +1,64 @@ +_conn->httpHeadersReceived = true; + } + + public function getConnectionClassString() { + return '\Ratchet\ConnectionInterface'; + } + + public function getDecoratorClassString() { + return '\Ratchet\Http\HttpServer'; + } + + public function getComponentClassString() { + return '\Ratchet\Http\HttpServerInterface'; + } + + public function testOpen() { + $headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"; + + $this->_conn->httpHeadersReceived = false; + $this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection()); + $this->_serv->onMessage($this->_conn, $headers); + } + + public function testOnMessageAfterHeaders() { + $headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"; + $this->_conn->httpHeadersReceived = false; + $this->_serv->onMessage($this->_conn, $headers); + + $message = "Hello World!"; + $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message); + $this->_serv->onMessage($this->_conn, $message); + } + + public function testBufferOverflow() { + $this->_conn->expects($this->once())->method('close'); + $this->_conn->httpHeadersReceived = false; + + $this->_serv->onMessage($this->_conn, str_repeat('a', 5000)); + } + + public function testCloseIfNotEstablished() { + $this->_conn->httpHeadersReceived = false; + $this->_conn->expects($this->once())->method('close'); + $this->_serv->onError($this->_conn, new \Exception('Whoops!')); + } + + public function testBufferHeaders() { + $this->_conn->httpHeadersReceived = false; + $this->_app->expects($this->never())->method('onOpen'); + $this->_app->expects($this->never())->method('onMessage'); + + $this->_serv->onMessage($this->_conn, "GET / HTTP/1.1"); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Http/OriginCheckTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Http/OriginCheckTest.php new file mode 100644 index 0000000..c1c4012 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Http/OriginCheckTest.php @@ -0,0 +1,46 @@ +_reqStub = $this->getMock('Psr\Http\Message\RequestInterface'); + $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue(['localhost'])); + + parent::setUp(); + + $this->_serv->allowedOrigins[] = 'localhost'; + } + + protected function doOpen($conn) { + $this->_serv->onOpen($conn, $this->_reqStub); + } + + public function getConnectionClassString() { + return '\Ratchet\ConnectionInterface'; + } + + public function getDecoratorClassString() { + return '\Ratchet\Http\OriginCheck'; + } + + public function getComponentClassString() { + return '\Ratchet\Http\HttpServerInterface'; + } + + public function testCloseOnNonMatchingOrigin() { + $this->_serv->allowedOrigins = ['socketo.me']; + $this->_conn->expects($this->once())->method('close'); + + $this->_serv->onOpen($this->_conn, $this->_reqStub); + } + + public function testOnMessage() { + $this->passthroughMessageTest('Hello World!'); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Http/RouterTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Http/RouterTest.php new file mode 100644 index 0000000..1ca4cbc --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Http/RouterTest.php @@ -0,0 +1,165 @@ +_conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_uri = $this->getMock('Psr\Http\Message\UriInterface'); + $this->_req = $this->getMock('\Psr\Http\Message\RequestInterface'); + $this->_req + ->expects($this->any()) + ->method('getUri') + ->will($this->returnValue($this->_uri)); + $this->_matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); + $this->_matcher + ->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext'))); + $this->_router = new Router($this->_matcher); + + $this->_uri->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); + $this->_uri->expects($this->any())->method('withQuery')->with($this->callback(function($val) { + $this->setResult($val); + + return true; + }))->will($this->returnSelf()); + $this->_uri->expects($this->any())->method('getQuery')->will($this->returnCallback([$this, 'getResult'])); + $this->_req->expects($this->any())->method('withUri')->will($this->returnSelf()); + } + + public function testFourOhFour() { + $this->_conn->expects($this->once())->method('close'); + + $nope = new ResourceNotFoundException; + $this->_matcher->expects($this->any())->method('match')->will($this->throwException($nope)); + + $this->_router->onOpen($this->_conn, $this->_req); + } + + public function testNullRequest() { + $this->setExpectedException('\UnexpectedValueException'); + $this->_router->onOpen($this->_conn); + } + + public function testControllerIsMessageComponentInterface() { + $this->setExpectedException('\UnexpectedValueException'); + $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => new \StdClass))); + $this->_router->onOpen($this->_conn, $this->_req); + } + + public function testControllerOnOpen() { + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); + $this->_router->onOpen($this->_conn, $this->_req); + + $expectedConn = new \PHPUnit_Framework_Constraint_IsInstanceOf('\Ratchet\ConnectionInterface'); + $controller->expects($this->once())->method('onOpen')->with($expectedConn, $this->_req); + + $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); + $this->_router->onOpen($this->_conn, $this->_req); + } + + public function testControllerOnMessageBubbles() { + $message = "The greatest trick the Devil ever pulled was convincing the world he didn't exist"; + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $controller->expects($this->once())->method('onMessage')->with($this->_conn, $message); + + $this->_conn->controller = $controller; + + $this->_router->onMessage($this->_conn, $message); + } + + public function testControllerOnCloseBubbles() { + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $controller->expects($this->once())->method('onClose')->with($this->_conn); + + $this->_conn->controller = $controller; + + $this->_router->onClose($this->_conn); + } + + public function testControllerOnErrorBubbles() { + $e= new \Exception('One cannot be betrayed if one has no exceptions'); + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $controller->expects($this->once())->method('onError')->with($this->_conn, $e); + + $this->_conn->controller = $controller; + + $this->_router->onError($this->_conn, $e); + } + + public function testRouterGeneratesRouteParameters() { + /** @var $controller WsServerInterface */ + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + /** @var $matcher UrlMatcherInterface */ + $this->_matcher->expects($this->any())->method('match')->will( + $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) + ); + $conn = $this->getMock('Ratchet\Mock\Connection'); + + $router = new Router($this->_matcher); + + $router->onOpen($conn, $this->_req); + + $this->assertEquals('foo=bar&baz=qux', $this->_req->getUri()->getQuery()); + } + + public function testQueryParams() { + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $this->_matcher->expects($this->any())->method('match')->will( + $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) + ); + + $conn = $this->getMock('Ratchet\Mock\Connection'); + $request = $this->getMock('Psr\Http\Message\RequestInterface'); + $uri = new \GuzzleHttp\Psr7\Uri('ws://doesnt.matter/endpoint?hello=world&foo=nope'); + + $request->expects($this->any())->method('getUri')->will($this->returnCallback(function() use (&$uri) { + return $uri; + })); + $request->expects($this->any())->method('withUri')->with($this->callback(function($url) use (&$uri) { + $uri = $url; + + return true; + }))->will($this->returnSelf()); + + $router = new Router($this->_matcher); + $router->onOpen($conn, $request); + + $this->assertEquals('foo=nope&baz=qux&hello=world', $request->getUri()->getQuery()); + $this->assertEquals('ws', $request->getUri()->getScheme()); + $this->assertEquals('doesnt.matter', $request->getUri()->getHost()); + } + + public function testImpatientClientOverflow() { + $this->_conn->expects($this->once())->method('close'); + + $header = "GET /nope HTTP/1.1 +Upgrade: websocket +Connection: upgrade +Host: localhost +Origin: http://localhost +Sec-WebSocket-Version: 13\r\n\r\n"; + + $app = new HttpServer(new Router(new UrlMatcher(new RouteCollection, new RequestContext))); + $app->onOpen($this->_conn); + $app->onMessage($this->_conn, $header); + $app->onMessage($this->_conn, 'Silly body'); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Server/EchoServerTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Server/EchoServerTest.php new file mode 100644 index 0000000..47fb0e2 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Server/EchoServerTest.php @@ -0,0 +1,26 @@ +_conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_comp = new EchoServer; + } + + public function testMessageEchod() { + $message = 'Tillsonburg, my back still aches when I hear that word.'; + $this->_conn->expects($this->once())->method('send')->with($message); + $this->_comp->onMessage($this->_conn, $message); + } + + public function testErrorClosesConnection() { + ob_start(); + $this->_conn->expects($this->once())->method('close'); + $this->_comp->onError($this->_conn, new \Exception); + ob_end_clean(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Server/FlashPolicyComponentTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Server/FlashPolicyComponentTest.php new file mode 100644 index 0000000..38fc96a --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Server/FlashPolicyComponentTest.php @@ -0,0 +1,152 @@ +_policy = new FlashPolicy(); + } + + public function testPolicyRender() { + $this->_policy->setSiteControl('all'); + $this->_policy->addAllowedAccess('example.com', '*'); + $this->_policy->addAllowedAccess('dev.example.com', '*'); + + $this->assertInstanceOf('SimpleXMLElement', $this->_policy->renderPolicy()); + } + + public function testInvalidPolicyReader() { + $this->setExpectedException('UnexpectedValueException'); + $this->_policy->renderPolicy(); + } + + public function testInvalidDomainPolicyReader() { + $this->setExpectedException('UnexpectedValueException'); + $this->_policy->setSiteControl('all'); + $this->_policy->addAllowedAccess('dev.example.*', '*'); + $this->_policy->renderPolicy(); + } + + /** + * @dataProvider siteControl + */ + public function testSiteControlValidation($accept, $permittedCrossDomainPolicies) { + $this->assertEquals($accept, $this->_policy->validateSiteControl($permittedCrossDomainPolicies)); + } + + public static function siteControl() { + return array( + array(true, 'all') + , array(true, 'none') + , array(true, 'master-only') + , array(false, 'by-content-type') + , array(false, 'by-ftp-filename') + , array(false, '') + , array(false, 'all ') + , array(false, 'asdf') + , array(false, '@893830') + , array(false, '*') + ); + } + + /** + * @dataProvider URI + */ + public function testDomainValidation($accept, $domain) { + $this->assertEquals($accept, $this->_policy->validateDomain($domain)); + } + + public static function URI() { + return array( + array(true, '*') + , array(true, 'example.com') + , array(true, 'exam-ple.com') + , array(true, '*.example.com') + , array(true, 'www.example.com') + , array(true, 'dev.dev.example.com') + , array(true, 'http://example.com') + , array(true, 'https://example.com') + , array(true, 'http://*.example.com') + , array(false, 'exam*ple.com') + , array(true, '127.0.255.1') + , array(true, 'localhost') + , array(false, 'www.example.*') + , array(false, 'www.exa*le.com') + , array(false, 'www.example.*com') + , array(false, '*.example.*') + , array(false, 'gasldf*$#a0sdf0a8sdf') + ); + } + + /** + * @dataProvider ports + */ + public function testPortValidation($accept, $ports) { + $this->assertEquals($accept, $this->_policy->validatePorts($ports)); + } + + public static function ports() { + return array( + array(true, '*') + , array(true, '80') + , array(true, '80,443') + , array(true, '507,516-523') + , array(true, '507,516-523,333') + , array(true, '507,516-523,507,516-523') + , array(false, '516-') + , array(true, '516-523,11') + , array(false, '516,-523,11') + , array(false, 'example') + , array(false, 'asdf,123') + , array(false, '--') + , array(false, ',,,') + , array(false, '838*') + ); + } + + public function testAddAllowedAccessOnlyAcceptsValidPorts() { + $this->setExpectedException('UnexpectedValueException'); + + $this->_policy->addAllowedAccess('*', 'nope'); + } + + public function testSetSiteControlThrowsException() { + $this->setExpectedException('UnexpectedValueException'); + + $this->_policy->setSiteControl('nope'); + } + + public function testErrorClosesConnection() { + $conn = $this->getMock('\\Ratchet\\ConnectionInterface'); + $conn->expects($this->once())->method('close'); + + $this->_policy->onError($conn, new \Exception); + } + + public function testOnMessageSendsString() { + $this->_policy->addAllowedAccess('*', '*'); + + $conn = $this->getMock('\\Ratchet\\ConnectionInterface'); + $conn->expects($this->once())->method('send')->with($this->isType('string')); + + $this->_policy->onMessage($conn, ' '); + } + + public function testOnOpenExists() { + $this->assertTrue(method_exists($this->_policy, 'onOpen')); + $conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_policy->onOpen($conn); + } + + public function testOnCloseExists() { + $this->assertTrue(method_exists($this->_policy, 'onClose')); + $conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_policy->onClose($conn); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Server/IoConnectionTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Server/IoConnectionTest.php new file mode 100644 index 0000000..07130f6 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Server/IoConnectionTest.php @@ -0,0 +1,32 @@ +sock = $this->getMock('\\React\\Socket\\ConnectionInterface'); + $this->conn = new IoConnection($this->sock); + } + + public function testCloseBubbles() { + $this->sock->expects($this->once())->method('end'); + $this->conn->close(); + } + + public function testSendBubbles() { + $msg = '6 hour rides are productive'; + + $this->sock->expects($this->once())->method('write')->with($msg); + $this->conn->send($msg); + } + + public function testSendReturnsSelf() { + $this->assertSame($this->conn, $this->conn->send('fluent interface')); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Server/IoServerTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Server/IoServerTest.php new file mode 100644 index 0000000..284fbde --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Server/IoServerTest.php @@ -0,0 +1,118 @@ +app = $this->getMock('\\Ratchet\\MessageComponentInterface'); + + $loop = new StreamSelectLoop; + $this->reactor = new Server(0, $loop); + + $uri = $this->reactor->getAddress(); + $this->port = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_PORT); + $this->server = new IoServer($this->app, $this->reactor, $loop); + } + + public function testOnOpen() { + $this->app->expects($this->once())->method('onOpen')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface')); + + $client = stream_socket_client("tcp://localhost:{$this->port}"); + + $this->server->loop->tick(); + + //$this->assertTrue(is_string($this->app->last['onOpen'][0]->remoteAddress)); + //$this->assertTrue(is_int($this->app->last['onOpen'][0]->resourceId)); + } + + public function testOnData() { + $msg = 'Hello World!'; + + $this->app->expects($this->once())->method('onMessage')->with( + $this->isInstanceOf('\\Ratchet\\ConnectionInterface') + , $msg + ); + + $client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1); + socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096); + socket_set_block($client); + socket_connect($client, 'localhost', $this->port); + + $this->server->loop->tick(); + + socket_write($client, $msg); + $this->server->loop->tick(); + + socket_shutdown($client, 1); + socket_shutdown($client, 0); + socket_close($client); + + $this->server->loop->tick(); + } + + public function testOnClose() { + $this->app->expects($this->once())->method('onClose')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface')); + + $client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1); + socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096); + socket_set_block($client); + socket_connect($client, 'localhost', $this->port); + + $this->server->loop->tick(); + + socket_shutdown($client, 1); + socket_shutdown($client, 0); + socket_close($client); + + $this->server->loop->tick(); + } + + public function testFactory() { + $this->assertInstanceOf('\\Ratchet\\Server\\IoServer', IoServer::factory($this->app, 0)); + } + + public function testNoLoopProvidedError() { + $this->setExpectedException('RuntimeException'); + + $io = new IoServer($this->app, $this->reactor); + $io->run(); + } + + public function testOnErrorPassesException() { + $conn = $this->getMock('\\React\\Socket\\ConnectionInterface'); + $conn->decor = $this->getMock('\\Ratchet\\ConnectionInterface'); + $err = new \Exception("Nope"); + + $this->app->expects($this->once())->method('onError')->with($conn->decor, $err); + + $this->server->handleError($err, $conn); + } + + public function onErrorCalledWhenExceptionThrown() { + $this->markTestIncomplete("Need to learn how to throw an exception from a mock"); + + $conn = $this->getMock('\\React\\Socket\\ConnectionInterface'); + $this->server->handleConnect($conn); + + $e = new \Exception; + $this->app->expects($this->once())->method('onMessage')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface'), 'f')->will($e); + $this->app->expects($this->once())->method('onError')->with($this->instanceOf('\\Ratchet\\ConnectionInterface', $e)); + + $this->server->handleData('f', $conn); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Server/IpBlackListComponentTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Server/IpBlackListComponentTest.php new file mode 100644 index 0000000..90f4185 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Server/IpBlackListComponentTest.php @@ -0,0 +1,125 @@ +mock = $this->getMock('\\Ratchet\\MessageComponentInterface'); + $this->blocker = new IpBlackList($this->mock); + } + + public function testOnOpen() { + $this->mock->expects($this->exactly(3))->method('onOpen'); + + $conn1 = $this->newConn(); + $conn2 = $this->newConn(); + $conn3 = $this->newConn(); + + $this->blocker->onOpen($conn1); + $this->blocker->onOpen($conn3); + $this->blocker->onOpen($conn2); + } + + public function testBlockDoesNotTriggerOnOpen() { + $conn = $this->newConn(); + + $this->blocker->blockAddress($conn->remoteAddress); + + $this->mock->expects($this->never())->method('onOpen'); + + $ret = $this->blocker->onOpen($conn); + } + + public function testBlockDoesNotTriggerOnClose() { + $conn = $this->newConn(); + + $this->blocker->blockAddress($conn->remoteAddress); + + $this->mock->expects($this->never())->method('onClose'); + + $ret = $this->blocker->onOpen($conn); + } + + public function testOnMessageDecoration() { + $conn = $this->newConn(); + $msg = 'Hello not being blocked'; + + $this->mock->expects($this->once())->method('onMessage')->with($conn, $msg); + + $this->blocker->onMessage($conn, $msg); + } + + public function testOnCloseDecoration() { + $conn = $this->newConn(); + + $this->mock->expects($this->once())->method('onClose')->with($conn); + + $this->blocker->onClose($conn); + } + + public function testBlockClosesConnection() { + $conn = $this->newConn(); + $this->blocker->blockAddress($conn->remoteAddress); + + $conn->expects($this->once())->method('close'); + + $this->blocker->onOpen($conn); + } + + public function testAddAndRemoveWithFluentInterfaces() { + $blockOne = '127.0.0.1'; + $blockTwo = '192.168.1.1'; + $unblock = '75.119.207.140'; + + $this->blocker + ->blockAddress($unblock) + ->blockAddress($blockOne) + ->unblockAddress($unblock) + ->blockAddress($blockTwo) + ; + + $this->assertEquals(array($blockOne, $blockTwo), $this->blocker->getBlockedAddresses()); + } + + public function testDecoratorPassesErrors() { + $conn = $this->newConn(); + $e = new \Exception('I threw an error'); + + $this->mock->expects($this->once())->method('onError')->with($conn, $e); + + $this->blocker->onError($conn, $e); + } + + public function addressProvider() { + return array( + array('127.0.0.1', '127.0.0.1') + , array('localhost', 'localhost') + , array('fe80::1%lo0', 'fe80::1%lo0') + , array('127.0.0.1', '127.0.0.1:6392') + ); + } + + /** + * @dataProvider addressProvider + */ + public function testFilterAddress($expected, $input) { + $this->assertEquals($expected, $this->blocker->filterAddress($input)); + } + + public function testUnblockingSilentlyFails() { + $this->assertInstanceOf('\\Ratchet\\Server\\IpBlackList', $this->blocker->unblockAddress('localhost')); + } + + protected function newConn() { + $conn = $this->getMock('\\Ratchet\\ConnectionInterface'); + $conn->remoteAddress = '127.0.0.1'; + + return $conn; + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Session/Serialize/PhpHandlerTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Session/Serialize/PhpHandlerTest.php new file mode 100644 index 0000000..4acf5bc --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Session/Serialize/PhpHandlerTest.php @@ -0,0 +1,43 @@ +_handler = new PhpHandler; + } + + public function serializedProvider() { + return array( + array( + '_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}' + , array( + '_sf2_attributes' => array( + 'hello' => 'world' + , 'last' => 1332872102 + ) + , '_sf2_flashes' => array() + ) + ) + ); + } + + /** + * @dataProvider serializedProvider + */ + public function testUnserialize($in, $expected) { + $this->assertEquals($expected, $this->_handler->unserialize($in)); + } + + /** + * @dataProvider serializedProvider + */ + public function testSerialize($serialized, $original) { + $this->assertEquals($serialized, $this->_handler->serialize($original)); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Session/SessionComponentTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Session/SessionComponentTest.php new file mode 100644 index 0000000..ebfdde4 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Session/SessionComponentTest.php @@ -0,0 +1,124 @@ +markTestSkipped('Dependency of Symfony HttpFoundation failed'); + } + + parent::setUp(); + $this->_serv = new SessionProvider($this->_app, new NullSessionHandler); + } + + public function tearDown() { + ini_set('session.serialize_handler', 'php'); + } + + public function getConnectionClassString() { + return '\Ratchet\ConnectionInterface'; + } + + public function getDecoratorClassString() { + return '\Ratchet\NullComponent'; + } + + public function getComponentClassString() { + return '\Ratchet\Http\HttpServerInterface'; + } + + public function classCaseProvider() { + return array( + array('php', 'Php') + , array('php_binary', 'PhpBinary') + ); + } + + /** + * @dataProvider classCaseProvider + */ + public function testToClassCase($in, $out) { + $ref = new \ReflectionClass('\\Ratchet\\Session\\SessionProvider'); + $method = $ref->getMethod('toClassCase'); + $method->setAccessible(true); + + $component = new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); + $this->assertEquals($out, $method->invokeArgs($component, array($in))); + } + + /** + * I think I have severely butchered this test...it's not so much of a unit test as it is a full-fledged component test + */ + public function testConnectionValueFromPdo() { + if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) { + return $this->markTestSkipped('Session test requires PDO and pdo_sqlite'); + } + + $sessionId = md5('testSession'); + + $dbOptions = array( + 'db_table' => 'sessions' + , 'db_id_col' => 'sess_id' + , 'db_data_col' => 'sess_data' + , 'db_time_col' => 'sess_time' + , 'db_lifetime_col' => 'sess_lifetime' + ); + + $pdo = new \PDO("sqlite::memory:"); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $pdo->exec(vsprintf("CREATE TABLE %s (%s TEXT NOT NULL PRIMARY KEY, %s BLOB NOT NULL, %s INTEGER NOT NULL, %s INTEGER)", $dbOptions)); + + $pdoHandler = new PdoSessionHandler($pdo, $dbOptions); + $pdoHandler->write($sessionId, '_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}'); + + $component = new SessionProvider($this->getMock($this->getComponentClassString()), $pdoHandler, array('auto_start' => 1)); + $connection = $this->getMock('Ratchet\\ConnectionInterface'); + + $headers = $this->getMock('Psr\Http\Message\RequestInterface'); + $headers->expects($this->once())->method('getHeader')->will($this->returnValue([ini_get('session.name') . "={$sessionId};"])); + + $component->onOpen($connection, $headers); + + $this->assertEquals('world', $connection->Session->get('hello')); + } + + protected function newConn() { + $conn = $this->getMock('Ratchet\ConnectionInterface'); + + $headers = $this->getMock('Psr\Http\Message\Request', array('getCookie'), array('POST', '/', array())); + $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue(null)); + + return $conn; + } + + public function testOnMessageDecorator() { + $message = "Database calls are usually blocking :("; + $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message); + $this->_serv->onMessage($this->_conn, $message); + } + + public function testRejectInvalidSeralizers() { + if (!function_exists('wddx_serialize_value')) { + $this->markTestSkipped(); + } + + ini_set('session.serialize_handler', 'wddx'); + $this->setExpectedException('\RuntimeException'); + new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); + } + + protected function doOpen($conn) { + $request = $this->getMock('Psr\Http\Message\RequestInterface'); + $request->expects($this->any())->method('getHeader')->will($this->returnValue([])); + + $this->_serv->onOpen($conn, $request); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php new file mode 100644 index 0000000..2727484 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php @@ -0,0 +1,53 @@ +markTestSkipped('Session test requires PDO and pdo_sqlite'); + } + + $schema = <<_pathToDB = tempnam(sys_get_temp_dir(), 'SQ3');; + $dsn = 'sqlite:' . $this->_pathToDB; + + $pdo = new \PDO($dsn); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $pdo->exec($schema); + $pdo = null; + + $sessionHandler = new PdoSessionHandler($dsn); + $serializer = new PhpHandler(); + $this->_virtualSessionStorage = new VirtualSessionStorage($sessionHandler, 'foobar', $serializer); + $this->_virtualSessionStorage->registerBag(new FlashBag()); + $this->_virtualSessionStorage->registerBag(new AttributeBag()); + } + + public function tearDown() { + unlink($this->_pathToDB); + } + + public function testStartWithDSN() { + $this->_virtualSessionStorage->start(); + + $this->assertTrue($this->_virtualSessionStorage->isStarted()); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/ServerProtocolTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/ServerProtocolTest.php new file mode 100644 index 0000000..8ff68c2 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/ServerProtocolTest.php @@ -0,0 +1,295 @@ +_app = new TestComponent; + $this->_comp = new ServerProtocol($this->_app); + } + + protected function newConn() { + return new Connection; + } + + public function invalidMessageProvider() { + return [ + [0] + , [3] + , [4] + , [8] + , [9] + ]; + } + + /** + * @dataProvider invalidMessageProvider + */ + public function testInvalidMessages($type) { + $this->setExpectedException('\Ratchet\Wamp\Exception'); + + $conn = $this->newConn(); + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, json_encode([$type])); + } + + public function testWelcomeMessage() { + $conn = $this->newConn(); + + $this->_comp->onOpen($conn); + + $message = $conn->last['send']; + $json = json_decode($message); + + $this->assertEquals(4, count($json)); + $this->assertEquals(0, $json[0]); + $this->assertTrue(is_string($json[1])); + $this->assertEquals(1, $json[2]); + } + + public function testSubscribe() { + $uri = 'http://example.com'; + $clientMessage = array(5, $uri); + + $conn = $this->newConn(); + + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, json_encode($clientMessage)); + + $this->assertEquals($uri, $this->_app->last['onSubscribe'][1]); + } + + public function testUnSubscribe() { + $uri = 'http://example.com/endpoint'; + $clientMessage = array(6, $uri); + + $conn = $this->newConn(); + + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, json_encode($clientMessage)); + + $this->assertEquals($uri, $this->_app->last['onUnSubscribe'][1]); + } + + public function callProvider() { + return [ + [2, 'a', 'b'] + , [2, ['a', 'b']] + , [1, 'one'] + , [3, 'one', 'two', 'three'] + , [3, ['un', 'deux', 'trois']] + , [2, 'hi', ['hello', 'world']] + , [2, ['hello', 'world'], 'hi'] + , [2, ['hello' => 'world', 'herp' => 'derp']] + ]; + } + + /** + * @dataProvider callProvider + */ + public function testCall() { + $args = func_get_args(); + $paramNum = array_shift($args); + + $uri = 'http://example.com/endpoint/' . rand(1, 100); + $id = uniqid('', false); + $clientMessage = array_merge(array(2, $id, $uri), $args); + + $conn = $this->newConn(); + + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, json_encode($clientMessage)); + + $this->assertEquals($id, $this->_app->last['onCall'][1]); + $this->assertEquals($uri, $this->_app->last['onCall'][2]); + + $this->assertEquals($paramNum, count($this->_app->last['onCall'][3])); + } + + public function testPublish() { + $conn = $this->newConn(); + + $topic = 'pubsubhubbub'; + $event = 'Here I am, publishing data'; + + $clientMessage = array(7, $topic, $event); + + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, json_encode($clientMessage)); + + $this->assertEquals($topic, $this->_app->last['onPublish'][1]); + $this->assertEquals($event, $this->_app->last['onPublish'][2]); + $this->assertEquals(array(), $this->_app->last['onPublish'][3]); + $this->assertEquals(array(), $this->_app->last['onPublish'][4]); + } + + public function testPublishAndExcludeMe() { + $conn = $this->newConn(); + + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, json_encode(array(7, 'topic', 'event', true))); + + $this->assertEquals($conn->WAMP->sessionId, $this->_app->last['onPublish'][3][0]); + } + + public function testPublishAndEligible() { + $conn = $this->newConn(); + + $buddy = uniqid('', false); + $friend = uniqid('', false); + + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, json_encode(array(7, 'topic', 'event', false, array($buddy, $friend)))); + + $this->assertEquals(array(), $this->_app->last['onPublish'][3]); + $this->assertEquals(2, count($this->_app->last['onPublish'][4])); + } + + public function eventProvider() { + return array( + array('http://example.com', array('one', 'two')) + , array('curie', array(array('hello' => 'world', 'herp' => 'derp'))) + ); + } + + /** + * @dataProvider eventProvider + */ + public function testEvent($topic, $payload) { + $conn = new WampConnection($this->newConn()); + $conn->event($topic, $payload); + + $eventString = $conn->last['send']; + + $this->assertSame(array(8, $topic, $payload), json_decode($eventString, true)); + } + + public function testOnClosePropagation() { + $conn = new Connection; + + $this->_comp->onOpen($conn); + $this->_comp->onClose($conn); + + $class = new \ReflectionClass('\\Ratchet\\Wamp\\WampConnection'); + $method = $class->getMethod('getConnection'); + $method->setAccessible(true); + + $check = $method->invokeArgs($this->_app->last['onClose'][0], array()); + + $this->assertSame($conn, $check); + } + + public function testOnErrorPropagation() { + $conn = new Connection; + + $e = new \Exception('Nope'); + + $this->_comp->onOpen($conn); + $this->_comp->onError($conn, $e); + + $class = new \ReflectionClass('\\Ratchet\\Wamp\\WampConnection'); + $method = $class->getMethod('getConnection'); + $method->setAccessible(true); + + $check = $method->invokeArgs($this->_app->last['onError'][0], array()); + + $this->assertSame($conn, $check); + $this->assertSame($e, $this->_app->last['onError'][1]); + } + + public function testPrefix() { + $conn = new WampConnection($this->newConn()); + $this->_comp->onOpen($conn); + + $prefix = 'incoming'; + $fullURI = "http://example.com/$prefix"; + $method = 'call'; + + $this->_comp->onMessage($conn, json_encode(array(1, $prefix, $fullURI))); + + $this->assertEquals($fullURI, $conn->WAMP->prefixes[$prefix]); + $this->assertEquals("$fullURI#$method", $conn->getUri("$prefix:$method")); + } + + public function testMessageMustBeJson() { + $this->setExpectedException('\\Ratchet\\Wamp\\JsonException'); + + $conn = new Connection; + + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, 'Hello World!'); + } + + public function testGetSubProtocolsReturnsArray() { + $this->assertTrue(is_array($this->_comp->getSubProtocols())); + } + + public function testGetSubProtocolsGetFromApp() { + $this->_app->protocols = array('hello', 'world'); + + $this->assertGreaterThanOrEqual(3, count($this->_comp->getSubProtocols())); + } + + public function testWampOnMessageApp() { + $app = $this->getMock('\\Ratchet\\Wamp\\WampServerInterface'); + $wamp = new ServerProtocol($app); + + $this->assertContains('wamp', $wamp->getSubProtocols()); + } + + public function badFormatProvider() { + return array( + array(json_encode(true)) + , array('{"valid":"json", "invalid": "message"}') + , array('{"0": "fail", "hello": "world"}') + ); + } + + /** + * @dataProvider badFormatProvider + */ + public function testValidJsonButInvalidProtocol($message) { + $this->setExpectedException('\Ratchet\Wamp\Exception'); + + $conn = $this->newConn(); + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, $message); + } + + public function testBadClientInputFromNonStringTopic() { + $this->setExpectedException('\Ratchet\Wamp\Exception'); + + $conn = new WampConnection($this->newConn()); + $this->_comp->onOpen($conn); + + $this->_comp->onMessage($conn, json_encode([5, ['hells', 'nope']])); + } + + public function testBadPrefixWithNonStringTopic() { + $this->setExpectedException('\Ratchet\Wamp\Exception'); + + $conn = new WampConnection($this->newConn()); + $this->_comp->onOpen($conn); + + $this->_comp->onMessage($conn, json_encode([1, ['hells', 'nope'], ['bad', 'input']])); + } + + public function testBadPublishWithNonStringTopic() { + $this->setExpectedException('\Ratchet\Wamp\Exception'); + + $conn = new WampConnection($this->newConn()); + $this->_comp->onOpen($conn); + + $this->_comp->onMessage($conn, json_encode([7, ['bad', 'input'], 'Hider'])); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicManagerTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicManagerTest.php new file mode 100644 index 0000000..b21b6bc --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicManagerTest.php @@ -0,0 +1,226 @@ +conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->mock = $this->getMock('\Ratchet\Wamp\WampServerInterface'); + $this->mngr = new TopicManager($this->mock); + + $this->conn->WAMP = new \StdClass; + $this->mngr->onOpen($this->conn); + } + + public function testGetTopicReturnsTopicObject() { + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); + $method = $class->getMethod('getTopic'); + $method->setAccessible(true); + + $topic = $method->invokeArgs($this->mngr, array('The Topic')); + + $this->assertInstanceOf('Ratchet\Wamp\Topic', $topic); + } + + public function testGetTopicCreatesTopicWithSameName() { + $name = 'The Topic'; + + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); + $method = $class->getMethod('getTopic'); + $method->setAccessible(true); + + $topic = $method->invokeArgs($this->mngr, array($name)); + + $this->assertEquals($name, $topic->getId()); + } + + public function testGetTopicReturnsSameObject() { + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); + $method = $class->getMethod('getTopic'); + $method->setAccessible(true); + + $topic = $method->invokeArgs($this->mngr, array('No copy')); + $again = $method->invokeArgs($this->mngr, array('No copy')); + + $this->assertSame($topic, $again); + } + + public function testOnOpen() { + $this->mock->expects($this->once())->method('onOpen'); + $this->mngr->onOpen($this->conn); + } + + public function testOnCall() { + $id = uniqid(); + + $this->mock->expects($this->once())->method('onCall')->with( + $this->conn + , $id + , $this->isInstanceOf('Ratchet\Wamp\Topic') + , array() + ); + + $this->mngr->onCall($this->conn, $id, 'new topic', array()); + } + + public function testOnSubscribeCreatesTopicObject() { + $this->mock->expects($this->once())->method('onSubscribe')->with( + $this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic') + ); + + $this->mngr->onSubscribe($this->conn, 'new topic'); + } + + public function testTopicIsInConnectionOnSubscribe() { + $name = 'New Topic'; + + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); + $method = $class->getMethod('getTopic'); + $method->setAccessible(true); + + $topic = $method->invokeArgs($this->mngr, array($name)); + + $this->mngr->onSubscribe($this->conn, $name); + + $this->assertTrue($this->conn->WAMP->subscriptions->contains($topic)); + } + + public function testDoubleSubscriptionFiresOnce() { + $this->mock->expects($this->exactly(1))->method('onSubscribe'); + + $this->mngr->onSubscribe($this->conn, 'same topic'); + $this->mngr->onSubscribe($this->conn, 'same topic'); + } + + public function testUnsubscribeEvent() { + $name = 'in and out'; + $this->mock->expects($this->once())->method('onUnsubscribe')->with( + $this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic') + ); + + $this->mngr->onSubscribe($this->conn, $name); + $this->mngr->onUnsubscribe($this->conn, $name); + } + + public function testUnsubscribeFiresOnce() { + $name = 'getting sleepy'; + $this->mock->expects($this->exactly(1))->method('onUnsubscribe'); + + $this->mngr->onSubscribe($this->conn, $name); + $this->mngr->onUnsubscribe($this->conn, $name); + $this->mngr->onUnsubscribe($this->conn, $name); + } + + public function testUnsubscribeRemovesTopicFromConnection() { + $name = 'Bye Bye Topic'; + + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); + $method = $class->getMethod('getTopic'); + $method->setAccessible(true); + + $topic = $method->invokeArgs($this->mngr, array($name)); + + $this->mngr->onSubscribe($this->conn, $name); + $this->mngr->onUnsubscribe($this->conn, $name); + + $this->assertFalse($this->conn->WAMP->subscriptions->contains($topic)); + } + + public function testOnPublishBubbles() { + $msg = 'Cover all the code!'; + + $this->mock->expects($this->once())->method('onPublish')->with( + $this->conn + , $this->isInstanceOf('Ratchet\Wamp\Topic') + , $msg + , $this->isType('array') + , $this->isType('array') + ); + + $this->mngr->onPublish($this->conn, 'topic coverage', $msg, array(), array()); + } + + public function testOnCloseBubbles() { + $this->mock->expects($this->once())->method('onClose')->with($this->conn); + $this->mngr->onClose($this->conn); + } + + protected function topicProvider($name) { + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); + $method = $class->getMethod('getTopic'); + $method->setAccessible(true); + + $attribute = $class->getProperty('topicLookup'); + $attribute->setAccessible(true); + + $topic = $method->invokeArgs($this->mngr, array($name)); + + return array($topic, $attribute); + } + + public function testConnIsRemovedFromTopicOnClose() { + $name = 'State Testing'; + list($topic, $attribute) = $this->topicProvider($name); + + $this->assertCount(1, $attribute->getValue($this->mngr)); + + $this->mngr->onSubscribe($this->conn, $name); + $this->mngr->onClose($this->conn); + + $this->assertFalse($topic->has($this->conn)); + } + + public static function topicConnExpectationProvider() { + return [ + [ 'onClose', 0] + , ['onUnsubscribe', 0] + ]; + } + + /** + * @dataProvider topicConnExpectationProvider + */ + public function testTopicRetentionFromLeavingConnections($methodCall, $expectation) { + $topicName = 'checkTopic'; + list($topic, $attribute) = $this->topicProvider($topicName); + + $this->mngr->onSubscribe($this->conn, $topicName); + call_user_func_array(array($this->mngr, $methodCall), array($this->conn, $topicName)); + + $this->assertCount($expectation, $attribute->getValue($this->mngr)); + } + + public function testOnErrorBubbles() { + $e = new \Exception('All work and no play makes Chris a dull boy'); + $this->mock->expects($this->once())->method('onError')->with($this->conn, $e); + + $this->mngr->onError($this->conn, $e); + } + + public function testGetSubProtocolsReturnsArray() { + $this->assertInternalType('array', $this->mngr->getSubProtocols()); + } + + public function testGetSubProtocolsBubbles() { + $subs = array('hello', 'world'); + $app = $this->getMock('Ratchet\Wamp\Stub\WsWampServerInterface'); + $app->expects($this->once())->method('getSubProtocols')->will($this->returnValue($subs)); + $mngr = new TopicManager($app); + + $this->assertEquals($subs, $mngr->getSubProtocols()); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicTest.php new file mode 100644 index 0000000..b8685b7 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/TopicTest.php @@ -0,0 +1,164 @@ +assertEquals($id, $topic->getId()); + } + + public function testAddAndCount() { + $topic = new Topic('merp'); + + $topic->add($this->newConn()); + $topic->add($this->newConn()); + $topic->add($this->newConn()); + + $this->assertEquals(3, count($topic)); + } + + public function testRemove() { + $topic = new Topic('boop'); + $tracked = $this->newConn(); + + $topic->add($this->newConn()); + $topic->add($tracked); + $topic->add($this->newConn()); + + $topic->remove($tracked); + + $this->assertEquals(2, count($topic)); + } + + public function testBroadcast() { + $msg = 'Hello World!'; + $name = 'Batman'; + $protocol = json_encode(array(8, $name, $msg)); + + $first = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + $second = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + + $first->expects($this->once()) + ->method('send') + ->with($this->equalTo($protocol)); + + $second->expects($this->once()) + ->method('send') + ->with($this->equalTo($protocol)); + + $topic = new Topic($name); + $topic->add($first); + $topic->add($second); + + $topic->broadcast($msg); + } + + public function testBroadcastWithExclude() { + $msg = 'Hello odd numbers'; + $name = 'Excluding'; + $protocol = json_encode(array(8, $name, $msg)); + + $first = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + $second = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + $third = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + + $first->expects($this->once()) + ->method('send') + ->with($this->equalTo($protocol)); + + $second->expects($this->never())->method('send'); + + $third->expects($this->once()) + ->method('send') + ->with($this->equalTo($protocol)); + + $topic = new Topic($name); + $topic->add($first); + $topic->add($second); + $topic->add($third); + + $topic->broadcast($msg, array($second->WAMP->sessionId)); + } + + public function testBroadcastWithEligible() { + $msg = 'Hello white list'; + $name = 'Eligible'; + $protocol = json_encode(array(8, $name, $msg)); + + $first = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + $second = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + $third = $this->getMock('Ratchet\\Wamp\\WampConnection', array('send'), array($this->getMock('\\Ratchet\\ConnectionInterface'))); + + $first->expects($this->once()) + ->method('send') + ->with($this->equalTo($protocol)); + + $second->expects($this->never())->method('send'); + + $third->expects($this->once()) + ->method('send') + ->with($this->equalTo($protocol)); + + $topic = new Topic($name); + $topic->add($first); + $topic->add($second); + $topic->add($third); + + $topic->broadcast($msg, array(), array($first->WAMP->sessionId, $third->WAMP->sessionId)); + } + + public function testIterator() { + $first = $this->newConn(); + $second = $this->newConn(); + $third = $this->newConn(); + + $topic = new Topic('Joker'); + $topic->add($first)->add($second)->add($third); + + $check = array($first, $second, $third); + + foreach ($topic as $mock) { + $this->assertNotSame(false, array_search($mock, $check)); + } + } + + public function testToString() { + $name = 'Bane'; + $topic = new Topic($name); + + $this->assertEquals($name, (string)$topic); + } + + public function testDoesHave() { + $conn = $this->newConn(); + $topic = new Topic('Two Face'); + $topic->add($conn); + + $this->assertTrue($topic->has($conn)); + } + + public function testDoesNotHave() { + $conn = $this->newConn(); + $topic = new Topic('Alfred'); + + $this->assertFalse($topic->has($conn)); + } + + public function testDoesNotHaveAfterRemove() { + $conn = $this->newConn(); + $topic = new Topic('Ras'); + + $topic->add($conn)->remove($conn); + + $this->assertFalse($topic->has($conn)); + } + + protected function newConn() { + return new WampConnection($this->getMock('\\Ratchet\\ConnectionInterface')); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampConnectionTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampConnectionTest.php new file mode 100644 index 0000000..adf59d5 --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampConnectionTest.php @@ -0,0 +1,77 @@ +mock = $this->getMock('\\Ratchet\\ConnectionInterface'); + $this->conn = new WampConnection($this->mock); + } + + public function testCallResult() { + $callId = uniqid(); + $data = array('hello' => 'world', 'herp' => 'derp'); + + $this->mock->expects($this->once())->method('send')->with(json_encode(array(3, $callId, $data))); + + $this->conn->callResult($callId, $data); + } + + public function testCallError() { + $callId = uniqid(); + $uri = 'http://example.com/end/point'; + + $this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, ''))); + + $this->conn->callError($callId, $uri); + } + + public function testCallErrorWithTopic() { + $callId = uniqid(); + $uri = 'http://example.com/end/point'; + + $this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, ''))); + + $this->conn->callError($callId, new Topic($uri)); + } + + public function testDetailedCallError() { + $callId = uniqid(); + $uri = 'http://example.com/end/point'; + $desc = 'beep boop beep'; + $detail = 'Error: Too much awesome'; + + $this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, $desc, $detail))); + + $this->conn->callError($callId, $uri, $desc, $detail); + } + + public function testPrefix() { + $shortOut = 'outgoing'; + $longOut = 'http://example.com/outgoing'; + + $this->mock->expects($this->once())->method('send')->with(json_encode(array(1, $shortOut, $longOut))); + + $this->conn->prefix($shortOut, $longOut); + } + + public function testGetUriWhenNoCurieGiven() { + $uri = 'http://example.com/noshort'; + + $this->assertEquals($uri, $this->conn->getUri($uri)); + } + + public function testClose() { + $mock = $this->getMock('\\Ratchet\\ConnectionInterface'); + $conn = new WampConnection($mock); + + $mock->expects($this->once())->method('close'); + + $conn->close(); + } +} diff --git a/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampServerTest.php b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampServerTest.php new file mode 100644 index 0000000..626b1ce --- /dev/null +++ b/assets/php/vendor/cboden/ratchet/tests/unit/Wamp/WampServerTest.php @@ -0,0 +1,49 @@ +_app->expects($this->once())->method('onPublish')->with( + $this->isExpectedConnection() + , new \PHPUnit_Framework_Constraint_IsInstanceOf('\Ratchet\Wamp\Topic') + , $published + , array() + , array() + ); + + $this->_serv->onMessage($this->_conn, json_encode(array(7, 'topic', $published))); + } + + public function testGetSubProtocols() { + // todo: could expand on this + $this->assertInternalType('array', $this->_serv->getSubProtocols()); + } + + public function testConnectionClosesOnInvalidJson() { + $this->_conn->expects($this->once())->method('close'); + $this->_serv->onMessage($this->_conn, 'invalid json'); + } + + public function testConnectionClosesOnProtocolError() { + $this->_conn->expects($this->once())->method('close'); + $this->_serv->onMessage($this->_conn, json_encode(array('valid' => 'json', 'invalid' => 'protocol'))); + } +} diff --git a/assets/php/vendor/composer/ClassLoader.php b/assets/php/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..dc02dfb --- /dev/null +++ b/assets/php/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * 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 + * @author Jordi Boggiano + * @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/assets/php/vendor/composer/LICENSE b/assets/php/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/assets/php/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/assets/php/vendor/composer/autoload_classmap.php b/assets/php/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..3a14165 --- /dev/null +++ b/assets/php/vendor/composer/autoload_classmap.php @@ -0,0 +1,16 @@ + $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/assets/php/vendor/composer/autoload_files.php b/assets/php/vendor/composer/autoload_files.php new file mode 100644 index 0000000..295c7b6 --- /dev/null +++ b/assets/php/vendor/composer/autoload_files.php @@ -0,0 +1,15 @@ + $vendorDir . '/react/promise/src/functions_include.php', + '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', + '6b06ce8ccf69c43a60a1e48495a034c9' => $vendorDir . '/react/promise-timer/src/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', +); diff --git a/assets/php/vendor/composer/autoload_namespaces.php b/assets/php/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..02066fb --- /dev/null +++ b/assets/php/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($vendorDir . '/evenement/evenement/src'), +); diff --git a/assets/php/vendor/composer/autoload_psr4.php b/assets/php/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..7240c63 --- /dev/null +++ b/assets/php/vendor/composer/autoload_psr4.php @@ -0,0 +1,26 @@ + array($baseDir . '/src'), + 'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'React\\Stream\\' => array($vendorDir . '/react/stream/src'), + 'React\\Socket\\' => array($vendorDir . '/react/socket/src'), + 'React\\Promise\\Timer\\' => array($vendorDir . '/react/promise-timer/src'), + 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), + 'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'), + 'React\\Dns\\' => array($vendorDir . '/react/dns/src'), + 'React\\Cache\\' => array($vendorDir . '/react/cache/src'), + 'Ratchet\\RFC6455\\' => array($vendorDir . '/ratchet/rfc6455/src'), + 'Ratchet\\' => array($vendorDir . '/cboden/ratchet/src/Ratchet'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'Nubs\\RandomNameGenerator\\' => array($vendorDir . '/nubs/random-name-generator/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), +); diff --git a/assets/php/vendor/composer/autoload_real.php b/assets/php/vendor/composer/autoload_real.php new file mode 100644 index 0000000..8fc42a0 --- /dev/null +++ b/assets/php/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 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\ComposerStaticInit3c5661e077098f105cbab5a541fd4883::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\ComposerStaticInit3c5661e077098f105cbab5a541fd4883::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire3c5661e077098f105cbab5a541fd4883($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire3c5661e077098f105cbab5a541fd4883($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/assets/php/vendor/composer/autoload_static.php b/assets/php/vendor/composer/autoload_static.php new file mode 100644 index 0000000..e598fbf --- /dev/null +++ b/assets/php/vendor/composer/autoload_static.php @@ -0,0 +1,157 @@ + __DIR__ . '/..' . '/react/promise/src/functions_include.php', + '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', + '6b06ce8ccf69c43a60a1e48495a034c9' => __DIR__ . '/..' . '/react/promise-timer/src/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'W' => + array ( + 'Websocket\\' => 10, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php70\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Component\\Routing\\' => 26, + 'Symfony\\Component\\HttpFoundation\\' => 33, + ), + 'R' => + array ( + 'React\\Stream\\' => 13, + 'React\\Socket\\' => 13, + 'React\\Promise\\Timer\\' => 20, + 'React\\Promise\\' => 14, + 'React\\EventLoop\\' => 16, + 'React\\Dns\\' => 10, + 'React\\Cache\\' => 12, + 'Ratchet\\RFC6455\\' => 16, + 'Ratchet\\' => 8, + ), + 'P' => + array ( + 'Psr\\Http\\Message\\' => 17, + ), + 'N' => + array ( + 'Nubs\\RandomNameGenerator\\' => 25, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Websocket\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + 'Symfony\\Polyfill\\Php70\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php70', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Component\\Routing\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/routing', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'React\\Stream\\' => + array ( + 0 => __DIR__ . '/..' . '/react/stream/src', + ), + 'React\\Socket\\' => + array ( + 0 => __DIR__ . '/..' . '/react/socket/src', + ), + 'React\\Promise\\Timer\\' => + array ( + 0 => __DIR__ . '/..' . '/react/promise-timer/src', + ), + 'React\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/react/promise/src', + ), + 'React\\EventLoop\\' => + array ( + 0 => __DIR__ . '/..' . '/react/event-loop/src', + ), + 'React\\Dns\\' => + array ( + 0 => __DIR__ . '/..' . '/react/dns/src', + ), + 'React\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/react/cache/src', + ), + 'Ratchet\\RFC6455\\' => + array ( + 0 => __DIR__ . '/..' . '/ratchet/rfc6455/src', + ), + 'Ratchet\\' => + array ( + 0 => __DIR__ . '/..' . '/cboden/ratchet/src/Ratchet', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Nubs\\RandomNameGenerator\\' => + array ( + 0 => __DIR__ . '/..' . '/nubs/random-name-generator/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'E' => + array ( + 'Evenement' => + array ( + 0 => __DIR__ . '/..' . '/evenement/evenement/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 = ComposerStaticInit3c5661e077098f105cbab5a541fd4883::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit3c5661e077098f105cbab5a541fd4883::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit3c5661e077098f105cbab5a541fd4883::$prefixesPsr0; + $loader->classMap = ComposerStaticInit3c5661e077098f105cbab5a541fd4883::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/assets/php/vendor/composer/installed.json b/assets/php/vendor/composer/installed.json new file mode 100644 index 0000000..0d41c5e --- /dev/null +++ b/assets/php/vendor/composer/installed.json @@ -0,0 +1,963 @@ +[ + { + "name": "cboden/ratchet", + "version": "v0.4.1", + "version_normalized": "0.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/Ratchet.git", + "reference": "0d31f3a8ad4795fd48397712709e55cd07f51360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/0d31f3a8ad4795fd48397712709e55cd07f51360", + "reference": "0d31f3a8ad4795fd48397712709e55cd07f51360", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.0", + "php": ">=5.4.2", + "ratchet/rfc6455": "^0.2", + "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", + "symfony/http-foundation": "^2.6|^3.0|^4.0", + "symfony/routing": "^2.6|^3.0|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2017-12-12T00:49:31+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ratchet\\": "src/Ratchet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + } + ], + "description": "PHP WebSocket library", + "homepage": "http://socketo.me", + "keywords": [ + "Ratchet", + "WebSockets", + "server", + "sockets", + "websocket" + ] + }, + { + "name": "evenement/evenement", + "version": "v3.0.1", + "version_normalized": "3.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "time": "2017-07-23T21:35:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ] + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "version_normalized": "1.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2017-03-20T17:10:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ] + }, + { + "name": "nubs/random-name-generator", + "version": "v2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nubs/random-name-generator.git", + "reference": "7004eb1724e1c4a154553e44312b7045fe412b77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nubs/random-name-generator/zipball/7004eb1724e1c4a154553e44312b7045fe412b77", + "reference": "7004eb1724e1c4a154553e44312b7045fe412b77", + "shasum": "" + }, + "require": { + "php": "~5.6 || ~7.0" + }, + "require-dev": { + "cinam/randomizer": ">=1.1.1,<2.0", + "phpunit/phpunit": "~5.0", + "satooshi/php-coveralls": "~1.0", + "squizlabs/php_codesniffer": "~2.3" + }, + "time": "2016-12-04T01:57:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Nubs\\RandomNameGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spencer Rinehart", + "email": "anubis@overthemonkey.com", + "role": "Developer" + } + ], + "description": "A library to create interesting, sometimes entertaining, random names.", + "keywords": [ + "alliteration", + "generator", + "random", + "video game" + ] + }, + { + "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": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ] + }, + { + "name": "ratchet/rfc6455", + "version": "v0.2.3", + "version_normalized": "0.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/RFC6455.git", + "reference": "cc8a1a46a703ce01de10fdb5fab387381b66edc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/cc8a1a46a703ce01de10fdb5fab387381b66edc8", + "reference": "cc8a1a46a703ce01de10fdb5fab387381b66edc8", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.0", + "php": ">=5.4.2" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "react/http": "^0.4.1", + "react/socket-client": "^0.4.3" + }, + "time": "2017-09-13T13:49:42+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ratchet\\RFC6455\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + } + ], + "description": "RFC6455 WebSocket protocol handler", + "homepage": "http://socketo.me", + "keywords": [ + "WebSockets", + "rfc6455", + "websocket" + ] + }, + { + "name": "react/cache", + "version": "v0.4.2", + "version_normalized": "0.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "75494f26b4ef089db9bf8c90b63c296246e099e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/75494f26b4ef089db9bf8c90b63c296246e099e8", + "reference": "75494f26b4ef089db9bf8c90b63c296246e099e8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "~2.0|~1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "time": "2017-12-20T16:47:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ] + }, + { + "name": "react/dns", + "version": "v0.4.13", + "version_normalized": "0.4.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7d1e08c300fd7de600810883386ee5e2a64898f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7d1e08c300fd7de600810883386ee5e2a64898f4", + "reference": "7d1e08c300fd7de600810883386ee5e2a64898f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "~0.4.0|~0.3.0", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "^2.1 || ^1.2.1", + "react/promise-timer": "^1.2", + "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "time": "2018-02-27T12:51:22+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ] + }, + { + "name": "react/event-loop", + "version": "v0.5.1", + "version_normalized": "0.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "e1e0647a5c6e2c86013a24e9c8252113df86105a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/e1e0647a5c6e2c86013a24e9c8252113df86105a", + "reference": "e1e0647a5c6e2c86013a24e9c8252113df86105a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" + }, + "suggest": { + "ext-event": "~1.0 for ExtEventLoop", + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "time": "2018-04-09T11:59:21+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ] + }, + { + "name": "react/promise", + "version": "v2.5.1", + "version_normalized": "2.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/62785ae604c8d69725d693eb370e1d67e94c4053", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2017-03-25T12:08:31+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ] + }, + { + "name": "react/promise-timer", + "version": "v1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "9b4cd9cbe7457e0d87fe8aa7ccceab8a2c830fbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/9b4cd9cbe7457e0d87fe8aa7ccceab8a2c830fbd", + "reference": "9b4cd9cbe7457e0d87fe8aa7ccceab8a2c830fbd", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "~2.1|~1.2" + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "time": "2017-12-22T15:41:41+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Promise\\Timer\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "homepage": "https://github.com/react/promise-timer", + "keywords": [ + "async", + "event-loop", + "promise", + "reactphp", + "timeout", + "timer" + ] + }, + { + "name": "react/socket", + "version": "v0.8.10", + "version_normalized": "0.8.10.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "d3957313c92b539537fccc80170c05a27ec25796" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/d3957313c92b539537fccc80170c05a27ec25796", + "reference": "d3957313c92b539537fccc80170c05a27ec25796", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^0.4.13", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "^2.1 || ^1.2", + "react/promise-timer": "~1.0", + "react/stream": "^1.0 || ^0.7.1" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "time": "2018-02-28T09:32:38+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ] + }, + { + "name": "react/stream", + "version": "v0.7.7", + "version_normalized": "0.7.7.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "10100896018fd847a257cd81143b8e1b7be08e40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/10100896018fd847a257cd81143b8e1b7be08e40", + "reference": "10100896018fd847a257cd81143b8e1b7be08e40", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "time": "2018-01-19T15:04:38+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Stream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ] + }, + { + "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" + ] + }, + { + "name": "symfony/routing", + "version": "v3.4.8", + "version_normalized": "3.4.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "50f333b707bef9f6972ad04e6df3ec8875c9a67c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/50f333b707bef9f6972ad04e6df3ec8875c9a67c", + "reference": "50f333b707bef9f6972ad04e6df3ec8875c9a67c", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "^3.3.1|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "time": "2018-04-04T13:22:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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 Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ] + } +] diff --git a/assets/php/vendor/evenement/evenement/.gitignore b/assets/php/vendor/evenement/evenement/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/assets/php/vendor/evenement/evenement/.travis.yml b/assets/php/vendor/evenement/evenement/.travis.yml new file mode 100644 index 0000000..65ba0ce --- /dev/null +++ b/assets/php/vendor/evenement/evenement/.travis.yml @@ -0,0 +1,24 @@ +language: php + +php: + - 7.0 + - 7.1 + - hhvm + - nightly + +matrix: + allow_failures: + - php: hhvm + - php: nightly + +before_script: + - wget http://getcomposer.org/composer.phar + - php composer.phar install + +script: + - ./vendor/bin/phpunit --coverage-text + - php -n examples/benchmark-emit-no-arguments.php + - php -n examples/benchmark-emit-one-argument.php + - php -n examples/benchmark-emit.php + - php -n examples/benchmark-emit-once.php + - php -n examples/benchmark-remove-listener-once.php diff --git a/assets/php/vendor/evenement/evenement/CHANGELOG.md b/assets/php/vendor/evenement/evenement/CHANGELOG.md new file mode 100644 index 0000000..568f229 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/CHANGELOG.md @@ -0,0 +1,35 @@ +CHANGELOG +========= + + +* v3.0.1 (2017-07-23) + + * Resolved regression introduced in once listeners in v3.0.0 [#49](https://github.com/igorw/evenement/pull/49) + +* v3.0.0 (2017-07-23) + + * Passing null as event name throw exception [#46](https://github.com/igorw/evenement/pull/46), and [#47](https://github.com/igorw/evenement/pull/47) + * Performance improvements [#39](https://github.com/igorw/evenement/pull/39), and [#45](https://github.com/igorw/evenement/pull/45) + * Remove once listeners [#44](https://github.com/igorw/evenement/pull/44), [#45](https://github.com/igorw/evenement/pull/45) + +* v2.1.0 (2017-07-17) + + * Chaining for "on" method [#30](https://github.com/igorw/evenement/pull/30) + * Unit tests (on Travis) improvements [#33](https://github.com/igorw/evenement/pull/33), [#36](https://github.com/igorw/evenement/pull/36), and [#37](https://github.com/igorw/evenement/pull/37) + * Benchmarks added [#35](https://github.com/igorw/evenement/pull/35), and [#40](https://github.com/igorw/evenement/pull/40) + * Minor performance improvements [#42](https://github.com/igorw/evenement/pull/42), and [#38](https://github.com/igorw/evenement/pull/38) + +* v2.0.0 (2012-11-02) + + * Require PHP >=5.4.0 + * Added EventEmitterTrait + * Removed EventEmitter2 + +* v1.1.0 (2017-07-17) + + * Chaining for "on" method [#29](https://github.com/igorw/evenement/pull/29) + * Minor performance improvements [#43](https://github.com/igorw/evenement/pull/43) + +* v1.0.0 (2012-05-30) + + * Inital stable release diff --git a/assets/php/vendor/evenement/evenement/LICENSE b/assets/php/vendor/evenement/evenement/LICENSE new file mode 100644 index 0000000..d9a37d0 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Igor Wiedler + +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/assets/php/vendor/evenement/evenement/README.md b/assets/php/vendor/evenement/evenement/README.md new file mode 100644 index 0000000..9443011 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/README.md @@ -0,0 +1,83 @@ +# Événement + +Événement is a very simple event dispatching library for PHP. + +It has the same design goals as [Silex](http://silex-project.org) and +[Pimple](http://pimple-project.org), to empower the user while staying concise +and simple. + +It is very strongly inspired by the EventEmitter API found in +[node.js](http://nodejs.org). + +[![Build Status](https://secure.travis-ci.org/igorw/evenement.png?branch=master)](http://travis-ci.org/igorw/evenement) + +## Fetch + +The recommended way to install Événement is [through composer](http://getcomposer.org). + +Just create a composer.json file for your project: + +```JSON +{ + "require": { + "evenement/evenement": "^3.0 || ^2.0" + } +} +``` + +**Note:** The `3.x` version of Événement requires PHP 7 and the `2.x` version requires PHP 5.4. If you are +using PHP 5.3, please use the `1.x` version: + +```JSON +{ + "require": { + "evenement/evenement": "^1.0" + } +} +``` + +And run these two commands to install it: + + $ curl -s http://getcomposer.org/installer | php + $ php composer.phar install + +Now you can add the autoloader, and you will have access to the library: + +```php +on('user.created', function (User $user) use ($logger) { + $logger->log(sprintf("User '%s' was created.", $user->getLogin())); +}); +``` + +### Emitting Events + +```php +emit('user.created', [$user]); +``` + +Tests +----- + + $ ./vendor/bin/phpunit + +License +------- +MIT, see LICENSE. diff --git a/assets/php/vendor/evenement/evenement/composer.json b/assets/php/vendor/evenement/evenement/composer.json new file mode 100644 index 0000000..cbb4827 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/composer.json @@ -0,0 +1,29 @@ +{ + "name": "evenement/evenement", + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": ["event-dispatcher", "event-emitter"], + "license": "MIT", + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "autoload-dev": { + "psr-0": { + "Evenement": "tests" + }, + "files": ["tests/Evenement/Tests/functions.php"] + } +} diff --git a/assets/php/vendor/evenement/evenement/doc/00-intro.md b/assets/php/vendor/evenement/evenement/doc/00-intro.md new file mode 100644 index 0000000..6c28a2a --- /dev/null +++ b/assets/php/vendor/evenement/evenement/doc/00-intro.md @@ -0,0 +1,28 @@ +# Introduction + +Événement is is French and means "event". The événement library aims to +provide a simple way of subscribing to events and notifying those subscribers +whenever an event occurs. + +The API that it exposes is almost a direct port of the EventEmitter API found +in node.js. It also includes an "EventEmitter". There are some minor +differences however. + +The EventEmitter is an implementation of the publish-subscribe pattern, which +is a generalized version of the observer pattern. The observer pattern +specifies an observable subject, which observers can register themselves to. +Once something interesting happens, the subject notifies its observers. + +Pub/sub takes the same idea but encapsulates the observation logic inside a +separate object which manages all of its subscribers or listeners. Subscribers +are bound to an event name, and will only receive notifications of the events +they subscribed to. + +**TLDR: What does evenement do, in short? It provides a mapping from event +names to a list of listener functions and triggers each listener for a given +event when it is emitted.** + +Why do we do this, you ask? To achieve decoupling. + +It allows you to design a system where the core will emit events, and modules +are able to subscribe to these events. And respond to them. diff --git a/assets/php/vendor/evenement/evenement/doc/01-api.md b/assets/php/vendor/evenement/evenement/doc/01-api.md new file mode 100644 index 0000000..17ba333 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/doc/01-api.md @@ -0,0 +1,91 @@ +# API + +The API that événement exposes is defined by the +`Evenement\EventEmitterInterface`. The interface is useful if you want to +define an interface that extends the emitter and implicitly defines certain +events to be emitted, or if you want to type hint an `EventEmitter` to be +passed to a method without coupling to the specific implementation. + +## on($event, callable $listener) + +Allows you to subscribe to an event. + +Example: + +```php +$emitter->on('user.created', function (User $user) use ($logger) { + $logger->log(sprintf("User '%s' was created.", $user->getLogin())); +}); +``` + +Since the listener can be any callable, you could also use an instance method +instead of the anonymous function: + +```php +$loggerSubscriber = new LoggerSubscriber($logger); +$emitter->on('user.created', array($loggerSubscriber, 'onUserCreated')); +``` + +This has the benefit that listener does not even need to know that the emitter +exists. + +You can also accept more than one parameter for the listener: + +```php +$emitter->on('numbers_added', function ($result, $a, $b) {}); +``` + +## once($event, callable $listener) + +Convenience method that adds a listener which is guaranteed to only be called +once. + +Example: + +```php +$conn->once('connected', function () use ($conn, $data) { + $conn->send($data); +}); +``` + +## emit($event, array $arguments = []) + +Emit an event, which will call all listeners. + +Example: + +```php +$conn->emit('data', [$data]); +``` + +The second argument to emit is an array of listener arguments. This is how you +specify more args: + +```php +$result = $a + $b; +$emitter->emit('numbers_added', [$result, $a, $b]); +``` + +## listeners($event) + +Allows you to inspect the listeners attached to an event. Particularly useful +to check if there are any listeners at all. + +Example: + +```php +$e = new \RuntimeException('Everything is broken!'); +if (0 === count($emitter->listeners('error'))) { + throw $e; +} +``` + +## removeListener($event, callable $listener) + +Remove a specific listener for a specific event. + +## removeAllListeners($event = null) + +Remove all listeners for a specific event or all listeners all together. This +is useful for long-running processes, where you want to remove listeners in +order to allow them to get garbage collected. diff --git a/assets/php/vendor/evenement/evenement/doc/02-plugin-system.md b/assets/php/vendor/evenement/evenement/doc/02-plugin-system.md new file mode 100644 index 0000000..6a08371 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/doc/02-plugin-system.md @@ -0,0 +1,155 @@ +# Example: Plugin system + +In this example I will show you how to create a generic plugin system with +événement where plugins can alter the behaviour of the app. The app is a blog. +Boring, I know. By using the EventEmitter it will be easy to extend this blog +with additional functionality without modifying the core system. + +The blog is quite basic. Users are able to create blog posts when they log in. +The users are stored in a static config file, so there is no sign up process. +Once logged in they get a "new post" link which gives them a form where they +can create a new blog post with plain HTML. That will store the post in a +document database. The index lists all blog post titles by date descending. +Clicking on the post title will take you to the full post. + +## Plugin structure + +The goal of the plugin system is to allow features to be added to the blog +without modifying any core files of the blog. + +The plugins are managed through a config file, `plugins.json`. This JSON file +contains a JSON-encoded list of class-names for plugin classes. This allows +you to enable and disable plugins in a central location. The initial +`plugins.json` is just an empty array: +```json +[] +``` + +A plugin class must implement the `PluginInterface`: +```php +interface PluginInterface +{ + function attachEvents(EventEmitterInterface $emitter); +} +``` + +The `attachEvents` method allows the plugin to attach any events to the +emitter. For example: +```php +class FooPlugin implements PluginInterface +{ + public function attachEvents(EventEmitterInterface $emitter) + { + $emitter->on('foo', function () { + echo 'bar!'; + }); + } +} +``` + +The blog system creates an emitter instance and loads the plugins: +```php +$emitter = new EventEmitter(); + +$pluginClasses = json_decode(file_get_contents('plugins.json'), true); +foreach ($pluginClasses as $pluginClass) { + $plugin = new $pluginClass(); + $pluginClass->attachEvents($emitter); +} +``` + +This is the base system. There are no plugins yet, and there are no events yet +either. That's because I don't know which extension points will be needed. I +will add them on demand. + +## Feature: Markdown + +Writing blog posts in HTML sucks! Wouldn't it be great if I could write them +in a nice format such as markdown, and have that be converted to HTML for me? + +This feature will need two extension points. I need to be able to mark posts +as markdown, and I need to be able to hook into the rendering of the post body +and convert it from markdown to HTML. So the blog needs two new events: +`post.create` and `post.render`. + +In the code that creates the post, I'll insert the `post.create` event: +```php +class PostEvent +{ + public $post; + + public function __construct(array $post) + { + $this->post = $post; + } +} + +$post = createPostFromRequest($_POST); + +$event = new PostEvent($post); +$emitter->emit('post.create', [$event]); +$post = $event->post; + +$db->save('post', $post); +``` + +This shows that you can wrap a value in an event object to make it mutable, +allowing listeners to change it. + +The same thing for the `post.render` event: +```php +public function renderPostBody(array $post) +{ + $emitter = $this->emitter; + + $event = new PostEvent($post); + $emitter->emit('post.render', [$event]); + $post = $event->post; + + return $post['body']; +} + +

+

+``` + +Ok, the events are in place. It's time to create the first plugin, woohoo! I +will call this the `MarkdownPlugin`, so here's `plugins.json`: +```json +[ + "MarkdownPlugin" +] +``` + +The `MarkdownPlugin` class will be autoloaded, so I don't have to worry about +including any files. I just have to worry about implementing the plugin class. +The `markdown` function represents a markdown to HTML converter. +```php +class MarkdownPlugin implements PluginInterface +{ + public function attachEvents(EventEmitterInterface $emitter) + { + $emitter->on('post.create', function (PostEvent $event) { + $event->post['format'] = 'markdown'; + }); + + $emitter->on('post.render', function (PostEvent $event) { + if (isset($event->post['format']) && 'markdown' === $event->post['format']) { + $event->post['body'] = markdown($event->post['body']); + } + }); + } +} +``` + +There you go, the blog now renders posts as markdown. But all of the previous +posts before the addition of the markdown plugin are still rendered correctly +as raw HTML. + +## Feature: Comments + +TODO + +## Feature: Comment spam control + +TODO diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php new file mode 100644 index 0000000..53d7f4b --- /dev/null +++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +const ITERATIONS = 10000000; + +use Evenement\EventEmitter; + +require __DIR__.'/../vendor/autoload.php'; + +$emitter = new EventEmitter(); + +$emitter->on('event', function () {}); + +$start = microtime(true); +for ($i = 0; $i < ITERATIONS; $i++) { + $emitter->emit('event'); +} +$time = microtime(true) - $start; + +echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL; diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit-once.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-once.php new file mode 100644 index 0000000..74f4d17 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-once.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +ini_set('memory_limit', '512M'); + +const ITERATIONS = 100000; + +use Evenement\EventEmitter; + +require __DIR__.'/../vendor/autoload.php'; + +$emitter = new EventEmitter(); + +for ($i = 0; $i < ITERATIONS; $i++) { + $emitter->once('event', function ($a, $b, $c) {}); +} + +$start = microtime(true); +$emitter->emit('event', [1, 2, 3]); +$time = microtime(true) - $start; + +echo 'Emitting one event to ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL; diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php new file mode 100644 index 0000000..39fc4ba --- /dev/null +++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +const ITERATIONS = 10000000; + +use Evenement\EventEmitter; + +require __DIR__.'/../vendor/autoload.php'; + +$emitter = new EventEmitter(); + +$emitter->on('event', function ($a) {}); + +$start = microtime(true); +for ($i = 0; $i < ITERATIONS; $i++) { + $emitter->emit('event', [1]); +} +$time = microtime(true) - $start; + +echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL; diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit.php new file mode 100644 index 0000000..3ab639e --- /dev/null +++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +const ITERATIONS = 10000000; + +use Evenement\EventEmitter; + +require __DIR__.'/../vendor/autoload.php'; + +$emitter = new EventEmitter(); + +$emitter->on('event', function ($a, $b, $c) {}); + +$start = microtime(true); +for ($i = 0; $i < ITERATIONS; $i++) { + $emitter->emit('event', [1, 2, 3]); +} +$time = microtime(true) - $start; + +echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL; diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php b/assets/php/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php new file mode 100644 index 0000000..414be3b --- /dev/null +++ b/assets/php/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +ini_set('memory_limit', '512M'); + +const ITERATIONS = 100000; + +use Evenement\EventEmitter; + +require __DIR__.'/../vendor/autoload.php'; + +$emitter = new EventEmitter(); + +$listeners = []; +for ($i = 0; $i < ITERATIONS; $i++) { + $listeners[] = function ($a, $b, $c) {}; +} + +$start = microtime(true); +foreach ($listeners as $listener) { + $emitter->once('event', $listener); +} +$time = microtime(true) - $start; +echo 'Adding ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL; + +$start = microtime(true); +foreach ($listeners as $listener) { + $emitter->removeListener('event', $listener); +} +$time = microtime(true) - $start; +echo 'Removing ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL; diff --git a/assets/php/vendor/evenement/evenement/phpunit.xml.dist b/assets/php/vendor/evenement/evenement/phpunit.xml.dist new file mode 100644 index 0000000..70bc693 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + ./tests/Evenement/ + + + + + + ./src/ + + + diff --git a/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitter.php b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitter.php new file mode 100644 index 0000000..db189b9 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitter.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +class EventEmitter implements EventEmitterInterface +{ + use EventEmitterTrait; +} diff --git a/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php new file mode 100644 index 0000000..310631a --- /dev/null +++ b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +interface EventEmitterInterface +{ + public function on($event, callable $listener); + public function once($event, callable $listener); + public function removeListener($event, callable $listener); + public function removeAllListeners($event = null); + public function listeners($event = null); + public function emit($event, array $arguments = []); +} diff --git a/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php new file mode 100644 index 0000000..a78e65c --- /dev/null +++ b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +use InvalidArgumentException; + +trait EventEmitterTrait +{ + protected $listeners = []; + protected $onceListeners = []; + + public function on($event, callable $listener) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + if (!isset($this->listeners[$event])) { + $this->listeners[$event] = []; + } + + $this->listeners[$event][] = $listener; + + return $this; + } + + public function once($event, callable $listener) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + if (!isset($this->onceListeners[$event])) { + $this->onceListeners[$event] = []; + } + + $this->onceListeners[$event][] = $listener; + + return $this; + } + + public function removeListener($event, callable $listener) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + if (isset($this->listeners[$event])) { + $index = \array_search($listener, $this->listeners[$event], true); + if (false !== $index) { + unset($this->listeners[$event][$index]); + if (\count($this->listeners[$event]) === 0) { + unset($this->listeners[$event]); + } + } + } + + if (isset($this->onceListeners[$event])) { + $index = \array_search($listener, $this->onceListeners[$event], true); + if (false !== $index) { + unset($this->onceListeners[$event][$index]); + if (\count($this->onceListeners[$event]) === 0) { + unset($this->onceListeners[$event]); + } + } + } + } + + public function removeAllListeners($event = null) + { + if ($event !== null) { + unset($this->listeners[$event]); + } else { + $this->listeners = []; + } + + if ($event !== null) { + unset($this->onceListeners[$event]); + } else { + $this->onceListeners = []; + } + } + + public function listeners($event = null): array + { + if ($event === null) { + $events = []; + $eventNames = \array_unique( + \array_merge(\array_keys($this->listeners), \array_keys($this->onceListeners)) + ); + foreach ($eventNames as $eventName) { + $events[$eventName] = \array_merge( + isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [], + isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : [] + ); + } + return $events; + } + + return \array_merge( + isset($this->listeners[$event]) ? $this->listeners[$event] : [], + isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : [] + ); + } + + public function emit($event, array $arguments = []) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + if (isset($this->listeners[$event])) { + foreach ($this->listeners[$event] as $listener) { + $listener(...$arguments); + } + } + + if (isset($this->onceListeners[$event])) { + $listeners = $this->onceListeners[$event]; + unset($this->onceListeners[$event]); + foreach ($listeners as $listener) { + $listener(...$arguments); + } + } + } +} diff --git a/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php new file mode 100644 index 0000000..28f3011 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php @@ -0,0 +1,438 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement\Tests; + +use Evenement\EventEmitter; +use InvalidArgumentException; +use PHPUnit\Framework\TestCase; + +class EventEmitterTest extends TestCase +{ + private $emitter; + + public function setUp() + { + $this->emitter = new EventEmitter(); + } + + public function testAddListenerWithLambda() + { + $this->emitter->on('foo', function () {}); + } + + public function testAddListenerWithMethod() + { + $listener = new Listener(); + $this->emitter->on('foo', [$listener, 'onFoo']); + } + + public function testAddListenerWithStaticMethod() + { + $this->emitter->on('bar', ['Evenement\Tests\Listener', 'onBar']); + } + + public function testAddListenerWithInvalidListener() + { + try { + $this->emitter->on('foo', 'not a callable'); + $this->fail(); + } catch (\Exception $e) { + } catch (\TypeError $e) { + } + } + + public function testOnce() + { + $listenerCalled = 0; + + $this->emitter->once('foo', function () use (&$listenerCalled) { + $listenerCalled++; + }); + + $this->assertSame(0, $listenerCalled); + + $this->emitter->emit('foo'); + + $this->assertSame(1, $listenerCalled); + + $this->emitter->emit('foo'); + + $this->assertSame(1, $listenerCalled); + } + + public function testOnceWithArguments() + { + $capturedArgs = []; + + $this->emitter->once('foo', function ($a, $b) use (&$capturedArgs) { + $capturedArgs = array($a, $b); + }); + + $this->emitter->emit('foo', array('a', 'b')); + + $this->assertSame(array('a', 'b'), $capturedArgs); + } + + public function testEmitWithoutArguments() + { + $listenerCalled = false; + + $this->emitter->on('foo', function () use (&$listenerCalled) { + $listenerCalled = true; + }); + + $this->assertSame(false, $listenerCalled); + $this->emitter->emit('foo'); + $this->assertSame(true, $listenerCalled); + } + + public function testEmitWithOneArgument() + { + $test = $this; + + $listenerCalled = false; + + $this->emitter->on('foo', function ($value) use (&$listenerCalled, $test) { + $listenerCalled = true; + + $test->assertSame('bar', $value); + }); + + $this->assertSame(false, $listenerCalled); + $this->emitter->emit('foo', ['bar']); + $this->assertSame(true, $listenerCalled); + } + + public function testEmitWithTwoArguments() + { + $test = $this; + + $listenerCalled = false; + + $this->emitter->on('foo', function ($arg1, $arg2) use (&$listenerCalled, $test) { + $listenerCalled = true; + + $test->assertSame('bar', $arg1); + $test->assertSame('baz', $arg2); + }); + + $this->assertSame(false, $listenerCalled); + $this->emitter->emit('foo', ['bar', 'baz']); + $this->assertSame(true, $listenerCalled); + } + + public function testEmitWithNoListeners() + { + $this->emitter->emit('foo'); + $this->emitter->emit('foo', ['bar']); + $this->emitter->emit('foo', ['bar', 'baz']); + } + + public function testEmitWithTwoListeners() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(2, $listenersCalled); + } + + public function testRemoveListenerMatching() + { + $listenersCalled = 0; + + $listener = function () use (&$listenersCalled) { + $listenersCalled++; + }; + + $this->emitter->on('foo', $listener); + $this->emitter->removeListener('foo', $listener); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(0, $listenersCalled); + } + + public function testRemoveListenerNotMatching() + { + $listenersCalled = 0; + + $listener = function () use (&$listenersCalled) { + $listenersCalled++; + }; + + $this->emitter->on('foo', $listener); + $this->emitter->removeListener('bar', $listener); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(1, $listenersCalled); + } + + public function testRemoveAllListenersMatching() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->removeAllListeners('foo'); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(0, $listenersCalled); + } + + public function testRemoveAllListenersNotMatching() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->removeAllListeners('bar'); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(1, $listenersCalled); + } + + public function testRemoveAllListenersWithoutArguments() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->on('bar', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->removeAllListeners(); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->emitter->emit('bar'); + $this->assertSame(0, $listenersCalled); + } + + public function testCallablesClosure() + { + $calledWith = null; + + $this->emitter->on('foo', function ($data) use (&$calledWith) { + $calledWith = $data; + }); + + $this->emitter->emit('foo', ['bar']); + + self::assertSame('bar', $calledWith); + } + + public function testCallablesClass() + { + $listener = new Listener(); + $this->emitter->on('foo', [$listener, 'onFoo']); + + $this->emitter->emit('foo', ['bar']); + + self::assertSame(['bar'], $listener->getData()); + } + + + public function testCallablesClassInvoke() + { + $listener = new Listener(); + $this->emitter->on('foo', $listener); + + $this->emitter->emit('foo', ['bar']); + + self::assertSame(['bar'], $listener->getMagicData()); + } + + public function testCallablesStaticClass() + { + $this->emitter->on('foo', '\Evenement\Tests\Listener::onBar'); + + $this->emitter->emit('foo', ['bar']); + + self::assertSame(['bar'], Listener::getStaticData()); + } + + public function testCallablesFunction() + { + $this->emitter->on('foo', '\Evenement\Tests\setGlobalTestData'); + + $this->emitter->emit('foo', ['bar']); + + self::assertSame('bar', $GLOBALS['evenement-evenement-test-data']); + + unset($GLOBALS['evenement-evenement-test-data']); + } + + public function testListeners() + { + $onA = function () {}; + $onB = function () {}; + $onC = function () {}; + $onceA = function () {}; + $onceB = function () {}; + $onceC = function () {}; + + self::assertCount(0, $this->emitter->listeners('event')); + $this->emitter->on('event', $onA); + self::assertCount(1, $this->emitter->listeners('event')); + self::assertSame([$onA], $this->emitter->listeners('event')); + $this->emitter->once('event', $onceA); + self::assertCount(2, $this->emitter->listeners('event')); + self::assertSame([$onA, $onceA], $this->emitter->listeners('event')); + $this->emitter->once('event', $onceB); + self::assertCount(3, $this->emitter->listeners('event')); + self::assertSame([$onA, $onceA, $onceB], $this->emitter->listeners('event')); + $this->emitter->on('event', $onB); + self::assertCount(4, $this->emitter->listeners('event')); + self::assertSame([$onA, $onB, $onceA, $onceB], $this->emitter->listeners('event')); + $this->emitter->removeListener('event', $onceA); + self::assertCount(3, $this->emitter->listeners('event')); + self::assertSame([$onA, $onB, $onceB], $this->emitter->listeners('event')); + $this->emitter->once('event', $onceC); + self::assertCount(4, $this->emitter->listeners('event')); + self::assertSame([$onA, $onB, $onceB, $onceC], $this->emitter->listeners('event')); + $this->emitter->on('event', $onC); + self::assertCount(5, $this->emitter->listeners('event')); + self::assertSame([$onA, $onB, $onC, $onceB, $onceC], $this->emitter->listeners('event')); + $this->emitter->once('event', $onceA); + self::assertCount(6, $this->emitter->listeners('event')); + self::assertSame([$onA, $onB, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event')); + $this->emitter->removeListener('event', $onB); + self::assertCount(5, $this->emitter->listeners('event')); + self::assertSame([$onA, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event')); + $this->emitter->emit('event'); + self::assertCount(2, $this->emitter->listeners('event')); + self::assertSame([$onA, $onC], $this->emitter->listeners('event')); + } + + public function testOnceCallIsNotRemovedWhenWorkingOverOnceListeners() + { + $aCalled = false; + $aCallable = function () use (&$aCalled) { + $aCalled = true; + }; + $bCalled = false; + $bCallable = function () use (&$bCalled, $aCallable) { + $bCalled = true; + $this->emitter->once('event', $aCallable); + }; + $this->emitter->once('event', $bCallable); + + self::assertFalse($aCalled); + self::assertFalse($bCalled); + $this->emitter->emit('event'); + + self::assertFalse($aCalled); + self::assertTrue($bCalled); + $this->emitter->emit('event'); + + self::assertTrue($aCalled); + self::assertTrue($bCalled); + } + + public function testEventNameMustBeStringOn() + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('event name must not be null'); + + $this->emitter->on(null, function () {}); + } + + public function testEventNameMustBeStringOnce() + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('event name must not be null'); + + $this->emitter->once(null, function () {}); + } + + public function testEventNameMustBeStringRemoveListener() + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('event name must not be null'); + + $this->emitter->removeListener(null, function () {}); + } + + public function testEventNameMustBeStringEmit() + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('event name must not be null'); + + $this->emitter->emit(null); + } + + public function testListenersGetAll() + { + $a = function () {}; + $b = function () {}; + $c = function () {}; + $d = function () {}; + + $this->emitter->once('event2', $c); + $this->emitter->on('event', $a); + $this->emitter->once('event', $b); + $this->emitter->on('event', $c); + $this->emitter->once('event', $d); + + self::assertSame( + [ + 'event' => [ + $a, + $c, + $b, + $d, + ], + 'event2' => [ + $c, + ], + ], + $this->emitter->listeners() + ); + } + + public function testOnceNestedCallRegression() + { + $first = 0; + $second = 0; + + $this->emitter->once('event', function () use (&$first, &$second) { + $first++; + $this->emitter->once('event', function () use (&$second) { + $second++; + }); + $this->emitter->emit('event'); + }); + $this->emitter->emit('event'); + + self::assertSame(1, $first); + self::assertSame(1, $second); + } +} diff --git a/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php new file mode 100644 index 0000000..df17424 --- /dev/null +++ b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement\Tests; + +class Listener +{ + private $data = []; + + private $magicData = []; + + private static $staticData = []; + + public function onFoo($data) + { + $this->data[] = $data; + } + + public function __invoke($data) + { + $this->magicData[] = $data; + } + + public static function onBar($data) + { + self::$staticData[] = $data; + } + + public function getData() + { + return $this->data; + } + + public function getMagicData() + { + return $this->magicData; + } + + public static function getStaticData() + { + return self::$staticData; + } +} diff --git a/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/functions.php b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/functions.php new file mode 100644 index 0000000..7f11f5b --- /dev/null +++ b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/functions.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement\Tests; + +function setGlobalTestData($data) +{ + $GLOBALS['evenement-evenement-test-data'] = $data; +} diff --git a/assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md b/assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md new file mode 100644 index 0000000..5c252b3 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md @@ -0,0 +1,110 @@ +# CHANGELOG + +## 1.4.2 - 2017-03-20 + +* Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing + calls to `trigger_error` when deprecated methods are invoked. + +## 1.4.1 - 2017-02-27 + +* Reverted BC break by reintroducing behavior to automagically fix a URI with a + relative path and an authority by adding a leading slash to the path. It's only + deprecated now. +* Added triggering of silenced deprecation warnings. + +## 1.4.0 - 2017-02-21 + +* Fix `Stream::read` when length parameter <= 0. +* `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +* Fix `ServerRequest::getUriFromGlobals` when `Host` header contains port. +* Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form. +* Allow `parse_response` to parse a response without delimiting space and reason. +* Ensure each URI modification results in a valid URI according to PSR-7 discussions. + Invalid modifications will throw an exception instead of returning a wrong URI or + doing some magic. + - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` +* Fix compatibility of URIs with `file` scheme and empty host. +* Added common URI utility methods based on RFC 3986 (see documentation in the readme): + - `Uri::isDefaultPort` + - `Uri::isAbsolute` + - `Uri::isNetworkPathReference` + - `Uri::isAbsolutePathReference` + - `Uri::isRelativePathReference` + - `Uri::isSameDocumentReference` + - `Uri::composeComponents` + - `UriNormalizer::normalize` + - `UriNormalizer::isEquivalent` + - `UriResolver::relativize` +* Deprecated `Uri::resolve` in favor of `UriResolver::resolve` +* Deprecated `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +## 1.3.1 - 2016-06-25 + +* Fix `Uri::__toString` for network path references, e.g. `//example.org`. +* Fix missing lowercase normalization for host. +* Fix handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +* Fix `Uri::withAddedHeader` to correctly merge headers with different case. +* Fix trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +* Fix `Uri::withAddedHeader` with an array of header values. +* Fix `Uri::resolve` when base path has no slash and handling of fragment. +* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +* Fix `ServerRequest::withoutAttribute` when attribute value is null. + +## 1.3.0 - 2016-04-13 + +* Added remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +* Added support for stream_for from scalars. +* Can now extend Uri. +* Fixed a bug in validating request methods by making it more permissive. + +## 1.2.3 - 2016-02-18 + +* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +* Fixed handling of gzipped responses with FNAME headers. + +## 1.2.2 - 2016-01-22 + +* Added support for URIs without any authority. +* Added support for HTTP 451 'Unavailable For Legal Reasons.' +* Added support for using '0' as a filename. +* Added support for including non-standard ports in Host headers. + +## 1.2.1 - 2015-11-02 + +* Now supporting negative offsets when seeking to SEEK_END. + +## 1.2.0 - 2015-08-15 + +* Body as `"0"` is now properly added to a response. +* Now allowing forward seeking in CachingStream. +* Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +* functions.php is now conditionally required. +* user-info is no longer dropped when resolving URIs. + +## 1.1.0 - 2015-06-24 + +* URIs can now be relative. +* `multipart/form-data` headers are now overridden case-insensitively. +* URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +* A port is no longer added to a URI when the scheme is missing and no port is + present. + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` diff --git a/assets/php/vendor/guzzlehttp/psr7/LICENSE b/assets/php/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000..581d95f --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling + +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/assets/php/vendor/guzzlehttp/psr7/README.md b/assets/php/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000..1649935 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,739 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + + +[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7) + + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\stream_for('abc, '); +$b = Psr7\stream_for('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\stream_for(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\stream_for(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\stream_for('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate deflate or gzipped content. + +This stream decorator skips the first 10 bytes of the given stream to remove +the gzip header, converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\stream_for('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Function API + +There are various functions available under the `GuzzleHttp\Psr7` namespace. + + +## `function str` + +`function str(MessageInterface $message)` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\str($request); +``` + + +## `function uri_for` + +`function uri_for($uri)` + +This function accepts a string or `Psr\Http\Message\UriInterface` and returns a +UriInterface for the given value. If the value is already a `UriInterface`, it +is returned as-is. + +```php +$uri = GuzzleHttp\Psr7\uri_for('http://example.com'); +assert($uri === GuzzleHttp\Psr7\uri_for($uri)); +``` + + +## `function stream_for` + +`function stream_for($resource = '', array $options = [])` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +* - metadata: Array of custom metadata. +* - size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\stream_for('foo'); +$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r')); + +$generator function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\stream_for($generator(100)); +``` + + +## `function parse_header` + +`function parse_header($header)` + +Parse an array of header values containing ";" separated data into an array of +associative arrays representing the header key value pair data of the header. +When a parameter does not contain a value, but just contains a key, this +function will inject a key with a '' string value. + + +## `function normalize_header` + +`function normalize_header($header)` + +Converts an array of header values that may contain comma separated headers +into an array of headers with no comma separated values. + + +## `function modify_request` + +`function modify_request(RequestInterface $request, array $changes)` + +Clone and modify a request with the given changes. This method is useful for +reducing the number of clones needed to mutate a message. + +The changes can be one of: + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `function rewind_body` + +`function rewind_body(MessageInterface $message)` + +Attempts to rewind a message body and throws an exception on failure. The body +of the message will only be rewound if a call to `tell()` returns a value other +than `0`. + + +## `function try_fopen` + +`function try_fopen($filename, $mode)` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an error +handler that checks for errors and throws an exception instead. + + +## `function copy_to_string` + +`function copy_to_string(StreamInterface $stream, $maxLen = -1)` + +Copy the contents of a stream into a string until the given number of bytes +have been read. + + +## `function copy_to_stream` + +`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)` + +Copy the contents of a stream into another stream until the given number of +bytes have been read. + + +## `function hash` + +`function hash(StreamInterface $stream, $algo, $rawOutput = false)` + +Calculate a hash of a Stream. This method reads the entire stream to calculate +a rolling hash (based on PHP's hash_init functions). + + +## `function readline` + +`function readline(StreamInterface $stream, $maxLength = null)` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `function parse_request` + +`function parse_request($message)` + +Parses a request message string into a request object. + + +## `function parse_response` + +`function parse_response($message)` + +Parses a response message string into a response object. + + +## `function parse_query` + +`function parse_query($str, $urlEncoding = true)` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key value pair +will become an array. This function does not parse nested PHP style arrays into +an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into +`['foo[a]' => '1', 'foo[b]' => '2']`). + + +## `function build_query` + +`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)` + +Build a query string from an array of key value pairs. + +This function can use the return value of parse_query() to build a query string. +This function does not modify the provided keys when an array is encountered +(like http_build_query would). + + +## `function mimetype_from_filename` + +`function mimetype_from_filename($filename)` + +Determines the mimetype of a file by looking at its extension. + + +## `function mimetype_from_extension` + +`function mimetype_from_extension($extension)` + +Maps a file extensions to a mimetype. + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers +do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. diff --git a/assets/php/vendor/guzzlehttp/psr7/composer.json b/assets/php/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000..b1c5a90 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,39 @@ +{ + "name": "guzzlehttp/psr7", + "type": "library", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": ["request", "response", "message", "stream", "http", "uri", "url"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/AppendStream.php b/assets/php/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000..23039fd --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,233 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $this->pos = $this->current = 0; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream + * + * {@inheritdoc} + */ + public function detach() + { + $this->close(); + $this->detached = true; + } + + public function tell() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind() + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + // Using a loose comparison here to match on '', false, and null + if ($result == null) { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/BufferStream.php b/assets/php/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000..af4d4c2 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,137 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + // TODO: What should happen here? + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/CachingStream.php b/assets/php/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000..ed68f08 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,138 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); + } + + public function getSize() + { + return max($this->stream->getSize(), $this->remoteStream->getSize()); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence == SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } + + private function cacheEntireStream() + { + $target = new FnStream(['write' => 'strlen']); + copy_to_stream($this, $target); + + return $this->tell(); + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/DroppingStream.php b/assets/php/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000..8935c80 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,42 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/FnStream.php b/assets/php/vendor/guzzlehttp/psr7/src/FnStream.php new file mode 100644 index 0000000..cc9b445 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/FnStream.php @@ -0,0 +1,149 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind() + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET) + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/InflateStream.php b/assets/php/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000..0051d3f --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,52 @@ +read(10); + $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header); + // Skip the header, that is 10 + length of filename + 1 (nil) bytes + $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength); + $resource = StreamWrapper::getResource($stream); + stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); + $this->stream = new Stream($resource); + } + + /** + * @param StreamInterface $stream + * @param $header + * @return int + */ + private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header) + { + $filename_header_length = 0; + + if (substr(bin2hex($header), 6, 2) === '08') { + // we have a filename, read until nil + $filename_header_length = 1; + while ($stream->read(1) !== chr(0)) { + $filename_header_length++; + } + } + + return $filename_header_length; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/assets/php/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000..02cec3a --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,39 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return stream_for(try_fopen($this->filename, $this->mode)); + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/LimitStream.php b/assets/php/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000..3c13d4f --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,155 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset % with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/MessageTrait.php b/assets/php/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000..1e4da64 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,183 @@ + array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface */ + private $stream; + + public function getProtocolVersion() + { + return $this->protocol; + } + + public function withProtocolVersion($version) + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($header) + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header) + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header) + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value) + { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value) + { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header) + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody() + { + if (!$this->stream) { + $this->stream = stream_for(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body) + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + private function setHeaders(array $headers) + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param string[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimHeaderValues(array $values) + { + return array_map(function ($value) { + return trim($value, " \t"); + }, $values); + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/MultipartStream.php b/assets/php/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000..c0fd584 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,153 @@ +boundary = $boundary ?: sha1(uniqid('', true)); + $this->stream = $this->createStream($elements); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getHeaders(array $headers) + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements) + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(stream_for("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element) + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = stream_for($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if (substr($uri, 0, 6) !== 'php://') { + $element['filename'] = $uri; + } + } + + list($body, $headers) = $this->createElement( + $element['name'], + $element['contents'], + isset($element['filename']) ? $element['filename'] : null, + isset($element['headers']) ? $element['headers'] : [] + ); + + $stream->addStream(stream_for($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(stream_for("\r\n")); + } + + /** + * @return array + */ + private function createElement($name, StreamInterface $stream, $filename, array $headers) + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf('form-data; name="%s"; filename="%s"', + $name, + basename($filename)) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = mimetype_from_filename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/assets/php/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000..2332218 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,22 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + try { + return copy_to_string($this); + } catch (\Exception $e) { + return ''; + } + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Request.php b/assets/php/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000..0828548 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,142 @@ +method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = stream_for($body); + } + } + + public function getRequestTarget() + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target == '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget) + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri() + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Response.php b/assets/php/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000..2830c6c --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,132 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 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 Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode = 200; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|null|resource|StreamInterface $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + $status = 200, + array $headers = [], + $body = null, + $version = '1.1', + $reason = null + ) { + $this->statusCode = (int) $status; + + if ($body !== '' && $body !== null) { + $this->stream = stream_for($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::$phrases[$this->statusCode])) { + $this->reasonPhrase = self::$phrases[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = '') + { + $new = clone $this; + $new->statusCode = (int) $code; + if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { + $reasonPhrase = self::$phrases[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + return $new; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/ServerRequest.php b/assets/php/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000..575aab8 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,358 @@ +serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * @throws InvalidArgumentException for unrecognized values + * @return array + */ + public static function normalizeFiles(array $files) + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * @return array|UploadedFileInterface + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @param array $files + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []) + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + * + * @return ServerRequestInterface + */ + public static function fromGlobals() + { + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + $headers = function_exists('getallheaders') ? getallheaders() : []; + $uri = self::getUriFromGlobals(); + $body = new LazyOpenStream('php://input', 'r+'); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + /** + * Get a Uri populated with values from $_SERVER. + * + * @return UriInterface + */ + public static function getUriFromGlobals() { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + $hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']); + $uri = $uri->withHost($hostHeaderParts[0]); + if (isset($hostHeaderParts[1])) { + $hasPort = true; + $uri = $uri->withPort($hostHeaderParts[1]); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI']); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + + /** + * {@inheritdoc} + */ + public function getServerParams() + { + return $this->serverParams; + } + + /** + * {@inheritdoc} + */ + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + /** + * {@inheritdoc} + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getCookieParams() + { + return $this->cookieParams; + } + + /** + * {@inheritdoc} + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getQueryParams() + { + return $this->queryParams; + } + + /** + * {@inheritdoc} + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * {@inheritdoc} + */ + public function withParsedBody($data) + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + /** + * {@inheritdoc} + */ + public function withAttribute($attribute, $value) + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withoutAttribute($attribute) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Stream.php b/assets/php/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000..e336628 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,257 @@ + [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ] + ]; + + /** + * This constructor accepts an associative array of options. + * + * - size: (int) If a read stream would otherwise have an indeterminate + * size, but the size is known due to foreknowledge, then you can + * provide that size, in bytes. + * - metadata: (array) Any additional metadata to return when the metadata + * of the stream is accessed. + * + * @param resource $stream Stream resource to wrap. + * @param array $options Associative array of options. + * + * @throws \InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $options = []) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Stream must be a resource'); + } + + if (isset($options['size'])) { + $this->size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } + + public function __get($name) + { + if ($name == 'stream') { + throw new \RuntimeException('The stream is detached'); + } + + throw new \BadMethodCallException('No value for ' . $name); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + try { + $this->seek(0); + return (string) stream_get_contents($this->stream); + } catch (\Exception $e) { + return ''; + } + } + + public function getContents() + { + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function close() + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + return !$this->stream || feof($this->stream); + } + + public function tell() + { + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } elseif (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length) + { + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + $string = fread($this->stream, $length); + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string) + { + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/assets/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..daec6f5 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,149 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @param string $name Name of the property (allows "stream" only). + * + * @return StreamInterface + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array([$this->stream, $method], $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/assets/php/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000..cf7b223 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,121 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, stream_context_create([ + 'guzzle' => ['stream' => $stream] + ])); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + $this->stream->seek($offset, $whence); + + return true; + } + + public function stream_stat() + { + static $modeMap = [ + 'r' => 33060, + 'r+' => 33206, + 'w' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/UploadedFile.php b/assets/php/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000..e62bd5c --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,316 @@ +setError($errorStatus); + $this->setSize($size); + $this->setClientFilename($clientFilename); + $this->setClientMediaType($clientMediaType); + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param mixed $streamOrFile + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile) + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @param int $error + * @throws InvalidArgumentException + */ + private function setError($error) + { + if (false === is_int($error)) { + throw new InvalidArgumentException( + 'Upload file error status must be an integer' + ); + } + + if (false === in_array($error, UploadedFile::$errors)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + /** + * @param int $size + * @throws InvalidArgumentException + */ + private function setSize($size) + { + if (false === is_int($size)) { + throw new InvalidArgumentException( + 'Upload file size must be an integer' + ); + } + + $this->size = $size; + } + + /** + * @param mixed $param + * @return boolean + */ + private function isStringOrNull($param) + { + return in_array(gettype($param), ['string', 'NULL']); + } + + /** + * @param mixed $param + * @return boolean + */ + private function isStringNotEmpty($param) + { + return is_string($param) && false === empty($param); + } + + /** + * @param string|null $clientFilename + * @throws InvalidArgumentException + */ + private function setClientFilename($clientFilename) + { + if (false === $this->isStringOrNull($clientFilename)) { + throw new InvalidArgumentException( + 'Upload file client filename must be a string or null' + ); + } + + $this->clientFilename = $clientFilename; + } + + /** + * @param string|null $clientMediaType + * @throws InvalidArgumentException + */ + private function setClientMediaType($clientMediaType) + { + if (false === $this->isStringOrNull($clientMediaType)) { + throw new InvalidArgumentException( + 'Upload file client media type must be a string or null' + ); + } + + $this->clientMediaType = $clientMediaType; + } + + /** + * Return true if there is no upload error + * + * @return boolean + */ + private function isOk() + { + return $this->error === UPLOAD_ERR_OK; + } + + /** + * @return boolean + */ + public function isMoved() + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive() + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + /** + * {@inheritdoc} + * @throws RuntimeException if the upload was not successful. + */ + public function getStream() + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + return new LazyOpenStream($this->file, 'r+'); + } + + /** + * {@inheritdoc} + * + * @see http://php.net/is_uploaded_file + * @see http://php.net/move_uploaded_file + * @param string $targetPath Path to which to move the uploaded file. + * @throws RuntimeException if the upload was not successful. + * @throws InvalidArgumentException if the $path specified is invalid. + * @throws RuntimeException on any error during the move operation, or on + * the second or subsequent call to the method. + */ + public function moveTo($targetPath) + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = php_sapi_name() == 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + copy_to_stream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + /** + * {@inheritdoc} + * + * @return int|null The file size in bytes or null if unknown. + */ + public function getSize() + { + return $this->size; + } + + /** + * {@inheritdoc} + * + * @see http://php.net/manual/en/features.file-upload.errors.php + * @return int One of PHP's UPLOAD_ERR_XXX constants. + */ + public function getError() + { + return $this->error; + } + + /** + * {@inheritdoc} + * + * @return string|null The filename sent by the client or null if none + * was provided. + */ + public function getClientFilename() + { + return $this->clientFilename; + } + + /** + * {@inheritdoc} + */ + public function getClientMediaType() + { + return $this->clientMediaType; + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Uri.php b/assets/php/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000..f46c1db --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,702 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; + private static $charSubDelims = '!\$&\'\(\)\*\+,;='; + private static $replaceQuery = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** + * @param string $uri URI to parse + */ + public function __construct($uri = '') + { + // weak type check to also accept null until we can add scalar type hints + if ($uri != '') { + $parts = parse_url($uri); + if ($parts === false) { + throw new \InvalidArgumentException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + + public function __toString() + { + return self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @param string $scheme + * @param string $authority + * @param string $path + * @param string $query + * @param string $fragment + * + * @return string + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents($scheme, $authority, $path, $query, $fragment) + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != ''|| $scheme === 'file') { + $uri .= '//' . $authority; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + * + * @param UriInterface $uri + * + * @return bool + */ + public static function isDefaultPort(UriInterface $uri) + { + return $uri->getPort() === null + || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @param UriInterface $uri + * + * @return bool + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri) + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri) + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Removes dot segments from a path and returns the new path. + * + * @param string $path + * + * @return string + * + * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead. + * @see UriResolver::removeDotSegments + */ + public static function removeDotSegments($path) + { + return UriResolver::removeDotSegments($path); + } + + /** + * Converts the relative URI into a new URI that is resolved against the base URI. + * + * @param UriInterface $base Base URI + * @param string|UriInterface $rel Relative URI + * + * @return UriInterface + * + * @deprecated since version 1.4. Use UriResolver::resolve instead. + * @see UriResolver::resolve + */ + public static function resolve(UriInterface $base, $rel) + { + if (!($rel instanceof UriInterface)) { + $rel = new self($rel); + } + + return UriResolver::resolve($base, $rel); + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + * + * @return UriInterface + */ + public static function withoutQueryValue(UriInterface $uri, $key) + { + $current = $uri->getQuery(); + if ($current === '') { + return $uri; + } + + $decodedKey = rawurldecode($key); + $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { + return rawurldecode(explode('=', $part)[0]) !== $decodedKey; + }); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + * + * @return UriInterface + */ + public static function withQueryValue(UriInterface $uri, $key, $value) + { + $current = $uri->getQuery(); + + if ($current === '') { + $result = []; + } else { + $decodedKey = rawurldecode($key); + $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { + return rawurldecode(explode('=', $part)[0]) !== $decodedKey; + }); + } + + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $key = strtr($key, self::$replaceQuery); + + if ($value !== null) { + $result[] = $key . '=' . strtr($value, self::$replaceQuery); + } else { + $result[] = $key; + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @param array $parts + * + * @return UriInterface + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws \InvalidArgumentException If the components do not form a valid URI. + */ + public static function fromParts(array $parts) + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null) + { + $info = $user; + if ($password != '') { + $info .= ':' . $password; + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->validateState(); + + return $new; + } + + public function withHost($host) + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->validateState(); + + return $new; + } + + public function withPort($port) + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path) + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->validateState(); + + return $new; + } + + public function withQuery($query) + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment) + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts) + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + + $this->removeDefaultPort(); + } + + /** + * @param string $scheme + * + * @return string + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme) + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return strtolower($scheme); + } + + /** + * @param string $host + * + * @return string + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host) + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return strtolower($host); + } + + /** + * @param int|null $port + * + * @return int|null + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port) + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (1 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 1 and 65535', $port) + ); + } + + return $port; + } + + private function removeDefaultPort() + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param string $path + * + * @return string + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path) + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string $str + * + * @return string + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str) + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match) + { + return rawurlencode($match[0]); + } + + private function validateState() + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + @trigger_error( + 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . + 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', + E_USER_DEPRECATED + ); + $this->path = '/'. $this->path; + //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty'); + } + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/assets/php/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000..384c29e --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,216 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS) + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri) + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri) + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/UriResolver.php b/assets/php/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000..c1cb8a2 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,219 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + * + * @param UriInterface $base Base URI + * @param UriInterface $target Target URI + * + * @return UriInterface The relative URI reference + */ + public static function relativize(UriInterface $base, UriInterface $target) + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target) + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment 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. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/functions.php b/assets/php/vendor/guzzlehttp/psr7/src/functions.php new file mode 100644 index 0000000..e40348d --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/functions.php @@ -0,0 +1,828 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + + return "{$msg}\r\n\r\n" . $message->getBody(); +} + +/** + * Returns a UriInterface for the given value. + * + * This function accepts a string or {@see Psr\Http\Message\UriInterface} and + * returns a UriInterface for the given value. If the value is already a + * `UriInterface`, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * @throws \InvalidArgumentException + */ +function uri_for($uri) +{ + if ($uri instanceof UriInterface) { + return $uri; + } elseif (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); +} + +/** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data + * @param array $options Additional options + * + * @return Stream + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ +function stream_for($resource = '', array $options = []) +{ + if (is_scalar($resource)) { + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + return new Stream($resource, $options); + case 'object': + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return stream_for((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(fopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); +} + +/** + * Parse an array of header values containing ";" separated data into an + * array of associative arrays representing the header key value pair + * data of the header. When a parameter does not contain a value, but just + * contains a key, this function will inject a key with a '' string value. + * + * @param string|array $header Header to parse into components. + * + * @return array Returns the parsed header values. + */ +function parse_header($header) +{ + static $trimmed = "\"' \n\t\r"; + $params = $matches = []; + + foreach (normalize_header($header) as $val) { + $part = []; + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; +} + +/** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @return array Returns the normalized header field values. + */ +function normalize_header($header) +{ + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; +} + +/** + * Clone and modify a request with the given changes. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + * + * @return RequestInterface + */ +function modify_request(RequestInterface $request, array $changes) +{ + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':'.$port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = _caseless_remove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + return new ServerRequest( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion(), + $request->getServerParams() + ); + } + + return new Request( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion() + ); +} + +/** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` returns a + * value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ +function rewind_body(MessageInterface $message) +{ + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } +} + +/** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * @throws \RuntimeException if the file cannot be opened + */ +function try_fopen($filename, $mode) +{ + $ex = null; + set_error_handler(function () use ($filename, $mode, &$ex) { + $ex = new \RuntimeException(sprintf( + 'Unable to open %s using mode %s: %s', + $filename, + $mode, + func_get_args()[1] + )); + }); + + $handle = fopen($filename, $mode); + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; +} + +/** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * @return string + * @throws \RuntimeException on error. + */ +function copy_to_string(StreamInterface $stream, $maxLen = -1) +{ + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; +} + +/** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ +function copy_to_stream( + StreamInterface $source, + StreamInterface $dest, + $maxLen = -1 +) { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } +} + +/** + * Calculate a hash of a Stream + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * @throws \RuntimeException on error. + */ +function hash( + StreamInterface $stream, + $algo, + $rawOutput = false +) { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; +} + +/** + * Read a line from the stream up to the maximum allowed buffer length + * + * @param StreamInterface $stream Stream to read from + * @param int $maxLength Maximum buffer length + * + * @return string|bool + */ +function readline(StreamInterface $stream, $maxLength = null) +{ + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + // Using a loose equality here to match on '' and false. + if (null == ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; +} + +/** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ +function parse_request($message) +{ + $data = _parse_message($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); +} + +/** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + * + * @return Response + */ +function parse_response($message) +{ + $data = _parse_message($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string'); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + isset($parts[2]) ? $parts[2] : null + ); +} + +/** + * Parse a query string into an associative array. + * + * If multiple values are found for the same key, the value of that key + * value pair will become an array. This function does not parse nested + * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will + * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']). + * + * @param string $str Query string to parse + * @param bool|string $urlEncoding How the query string is encoded + * + * @return array + */ +function parse_query($str, $urlEncoding = true) +{ + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($urlEncoding == PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding == PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { return $str; }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; +} + +/** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of parse_query() to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like http_build_query would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * @return string + */ +function build_query(array $params, $encoding = PHP_QUERY_RFC3986) +{ + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function ($str) { return $str; }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder($k); + if (!is_array($v)) { + $qs .= $k; + if ($v !== null) { + $qs .= '=' . $encoder($v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + if ($vv !== null) { + $qs .= '=' . $encoder($vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; +} + +/** + * Determines the mimetype of a file by looking at its extension. + * + * @param $filename + * + * @return null|string + */ +function mimetype_from_filename($filename) +{ + return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); +} + +/** + * Maps a file extensions to a mimetype. + * + * @param $extension string The file extension. + * + * @return string|null + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + */ +function mimetype_from_extension($extension) +{ + static $mimetypes = [ + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimetypes[$extension]) + ? $mimetypes[$extension] + : null; +} + +/** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + * @internal + */ +function _parse_message($message) +{ + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => '']; + array_shift($lines); + + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + $line = $lines[$i]; + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $result['body'] = implode('', array_slice($lines, $i + 2)); + } + break; + } + if (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + $result['headers'][$key][] = $value; + } + } + + return $result; +} + +/** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + * @internal + */ +function _parse_request_uri($path, array $headers) +{ + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); +} + +/** @internal */ +function _caseless_remove($keys, array $data) +{ + $result = []; + + foreach ($keys as &$key) { + $key = strtolower($key); + } + + foreach ($data as $k => $v) { + if (!in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; +} diff --git a/assets/php/vendor/guzzlehttp/psr7/src/functions_include.php b/assets/php/vendor/guzzlehttp/psr7/src/functions_include.php new file mode 100644 index 0000000..96a4a83 --- /dev/null +++ b/assets/php/vendor/guzzlehttp/psr7/src/functions_include.php @@ -0,0 +1,6 @@ + + +CMD ["./build.php"] diff --git a/assets/php/vendor/nubs/random-name-generator/LICENSE b/assets/php/vendor/nubs/random-name-generator/LICENSE new file mode 100644 index 0000000..4efa9e9 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2016 Spencer Rinehart + +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/assets/php/vendor/nubs/random-name-generator/README.md b/assets/php/vendor/nubs/random-name-generator/README.md new file mode 100644 index 0000000..70d76cf --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/README.md @@ -0,0 +1,91 @@ +# Random Name Generator +A PHP library to create interesting, sometimes entertaining, random names. + +[![Build Status](http://img.shields.io/travis/nubs/random-name-generator.svg?style=flat)](https://travis-ci.org/nubs/random-name-generator) +[![Scrutinizer Code Quality](http://img.shields.io/scrutinizer/g/nubs/random-name-generator.svg?style=flat)](https://scrutinizer-ci.com/g/nubs/random-name-generator/) +[![Coverage Status](https://coveralls.io/repos/nubs/random-name-generator/badge.svg?branch=master&service=github)](https://coveralls.io/github/nubs/random-name-generator?branch=master) + +[![Latest Stable Version](http://img.shields.io/packagist/v/nubs/random-name-generator.svg?style=flat)](https://packagist.org/packages/nubs/random-name-generator) +[![Total Downloads](http://img.shields.io/packagist/dt/nubs/random-name-generator.svg?style=flat)](https://packagist.org/packages/nubs/random-name-generator) +[![License](http://img.shields.io/packagist/l/nubs/random-name-generator.svg?style=flat)](https://packagist.org/packages/nubs/random-name-generator) + +[![Dependency Status](https://www.versioneye.com/user/projects/537d561814c15855aa000019/badge.svg?style=flat)](https://www.versioneye.com/user/projects/537d561814c15855aa000019) + +## Requirements +This library requires PHP 5.6, or newer. + +## Installation +This package uses [composer](https://getcomposer.org) so you can just add +`nubs/random-name-generator` as a dependency to your `composer.json` file or +execute the following command: + +```bash +composer require nubs/random-name-generator +``` + +## Generators + +### All +The "all" generator will utilize all other configured generators to generate +random names. It will select from the list of generators randomly and then +use them to generate a random name using their functionality. + +#### Usage +```php +$generator = \Nubs\RandomNameGenerator\All::create(); +echo $generator->getName(); +``` + +Alternatively, if you want to configure/build the generators to use instead of +using all of the available generators, you can construct them yourself: + +```php +$generator = new \Nubs\RandomNameGenerator\All( + [ + new \Nubs\RandomNameGenerator\Alliteration(), + new \Nubs\RandomNameGenerator\Vgng() + ] +); +``` + +### Video Game Names +The video game name generator is based off of [prior](http://videogamena.me/) +[art](https://github.com/nullpuppy/vgng). It will generate unique names based +off of "typical" video games. + +#### Examples +* *Kamikaze Bubblegum Warrior* +* *Rockin' Valkyrie Gaiden* +* *Neurotic Jackhammer Detective* +* *My Little Mountain Climber Conflict* +* *Small-Time Princess vs. The Space Mutants* + +You can also use [this web example](http://sam.sbat.com/) to see more example +video game names generated by this library. + +#### Usage +```php +$generator = new \Nubs\RandomNameGenerator\Vgng(); +echo $generator->getName(); +``` + +## Alliterative Names +The alliteration name generator is based off of a list of +[adjectives](http://grammar.yourdictionary.com/parts-of-speech/adjectives/list-of-adjective-words.html) +and a list of [animals](https://animalcorner.co.uk/animals/). + +#### Examples +* *Agreeable Anaconda* +* *Disturbed Duck* +* *Misty Meerkat* +* *Prickly Pig* + +#### Usage +```php +$generator = new \Nubs\RandomNameGenerator\Alliteration(); +echo $generator->getName(); +``` + +## License +random-name-generator is licensed under the MIT license. See +[LICENSE](LICENSE) for the full license text. diff --git a/assets/php/vendor/nubs/random-name-generator/build.php b/assets/php/vendor/nubs/random-name-generator/build.php new file mode 100755 index 0000000..f285d55 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/build.php @@ -0,0 +1,25 @@ +#!/usr/bin/env php +process(['standard' => ['PSR1'], 'files' => ['src', 'tests', 'build.php']]); +if ($phpcsViolations > 0) { + exit(1); +} + +$phpunitConfiguration = PHPUnit_Util_Configuration::getInstance(__DIR__ . '/phpunit.xml'); +$phpunitArguments = ['coverageHtml' => __DIR__ . '/coverage', 'configuration' => $phpunitConfiguration]; +$testRunner = new PHPUnit_TextUI_TestRunner(); +$result = $testRunner->doRun($phpunitConfiguration->getTestSuiteConfiguration(), $phpunitArguments, false); +if (!$result->wasSuccessful()) { + exit(1); +} + +$coverageReport = $result->getCodeCoverage()->getReport(); +if ($coverageReport->getNumExecutedLines() !== $coverageReport->getNumExecutableLines()) { + file_put_contents('php://stderr', "Code coverage was NOT 100%\n"); + exit(1); +} + +file_put_contents('php://stderr', "Code coverage was 100%\n"); diff --git a/assets/php/vendor/nubs/random-name-generator/composer.json b/assets/php/vendor/nubs/random-name-generator/composer.json new file mode 100644 index 0000000..d91fbff --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/composer.json @@ -0,0 +1,30 @@ +{ + "name": "nubs/random-name-generator", + "description": "A library to create interesting, sometimes entertaining, random names.", + "keywords": ["random", "generator", "video game", "alliteration"], + "authors": [ + { + "name": "Spencer Rinehart", + "email": "anubis@overthemonkey.com", + "role": "Developer" + } + ], + "license": "MIT", + "require": { + "php": "~5.6 || ~7.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.0", + "satooshi/php-coveralls": "~1.0", + "cinam/randomizer": ">=1.1.1,<2.0", + "squizlabs/php_codesniffer": "~2.3" + }, + "autoload": { + "psr-4": { + "Nubs\\RandomNameGenerator\\": "src" + } + }, + "scripts": { + "test": "./build.php" + } +} diff --git a/assets/php/vendor/nubs/random-name-generator/composer.lock b/assets/php/vendor/nubs/random-name-generator/composer.lock new file mode 100644 index 0000000..77d0fdb --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/composer.lock @@ -0,0 +1,1963 @@ +{ + "_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" + ], + "hash": "4b2c771eec058567f987575b9b3199a2", + "content-hash": "826850e9b398ef46a5bc5fec486788f6", + "packages": [], + "packages-dev": [ + { + "name": "cinam/randomizer", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/cinam/randomizer.git", + "reference": "beca7e3ad5b93cebdb897cd47247b19a109b970f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cinam/randomizer/zipball/beca7e3ad5b93cebdb897cd47247b19a109b970f", + "reference": "beca7e3ad5b93cebdb897cd47247b19a109b970f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cinam\\Randomizer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "cinam", + "email": "cinam@hotmail.com" + } + ], + "description": "Tools for generating random values.", + "homepage": "http://github.com/cinam/randomizer", + "keywords": [ + "random", + "random numbers", + "random values" + ], + "time": "2014-06-01 07:27:32" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2015-03-18 18:23:50" + }, + { + "name": "myclabs/deep-copy", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "a8773992b362b58498eed24bf85005f363c34771" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771", + "reference": "a8773992b362b58498eed24bf85005f363c34771", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2015-11-20 12:04:31" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.4.0", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2016-07-26 14:39:29" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "5.5.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46ec2d1522ae8c9a12aca6b7650e0be78bbb0502" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46ec2d1522ae8c9a12aca6b7650e0be78bbb0502", + "reference": "46ec2d1522ae8c9a12aca6b7650e0be78bbb0502", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "^4.0.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "^1.3 || ^2.0", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/object-enumerator": "~1.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-08-18 11:10:44" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "4e83390f64e7ce04fcaec2ce95cd72823b431d19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/4e83390f64e7ce04fcaec2ce95cd72823b431d19", + "reference": "4e83390f64e7ce04fcaec2ce95cd72823b431d19", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-08-17 09:33:51" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "satooshi/php-coveralls", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/satooshi/php-coveralls.git", + "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/da51d304fe8622bf9a6da39a8446e7afd432115c", + "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8|^3.0", + "php": ">=5.3.3", + "psr/log": "^1.0", + "symfony/config": "^2.1|^3.0", + "symfony/console": "^2.1|^3.0", + "symfony/stopwatch": "^2.0|^3.0", + "symfony/yaml": "^2.0|^3.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/satooshi/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2016-01-20 17:35:46" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-02-13 06:45:14" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18 05:49:44" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/object-enumerator", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2016-01-28 13:25:10" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, + { + "name": "sebastian/version", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-02-04 12:56:52" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "4edb770cb853def6e60c93abb088ad5ac2010c83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/4edb770cb853def6e60c93abb088ad5ac2010c83", + "reference": "4edb770cb853def6e60c93abb088ad5ac2010c83", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2016-07-13 23:29:13" + }, + { + "name": "symfony/config", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "a7630397b91be09cdd2fe57fd13612e258700598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/a7630397b91be09cdd2fe57fd13612e258700598", + "reference": "a7630397b91be09cdd2fe57fd13612e258700598", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "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 Config Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:04:17" + }, + { + "name": "symfony/console", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "f9e638e8149e9e41b570ff092f8007c477ef0ce5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/f9e638e8149e9e41b570ff092f8007c477ef0ce5", + "reference": "f9e638e8149e9e41b570ff092f8007c477ef0ce5", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "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 Console Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:04:17" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8", + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2016-07-28 16:56:28" + }, + { + "name": "symfony/filesystem", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "bb29adceb552d202b6416ede373529338136e84f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/bb29adceb552d202b6416ede373529338136e84f", + "reference": "bb29adceb552d202b6416ede373529338136e84f", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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 Filesystem Component", + "homepage": "https://symfony.com", + "time": "2016-07-20 05:44:26" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "dff51f72b0706335131b00a7f49606168c582594" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", + "reference": "dff51f72b0706335131b00a7f49606168c582594", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-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": "2016-05-18 14:26:46" + }, + { + "name": "symfony/stopwatch", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "bb42806b12c5f89db4ebf64af6741afe6d8457e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/bb42806b12c5f89db4ebf64af6741afe6d8457e1", + "reference": "bb42806b12c5f89db4ebf64af6741afe6d8457e1", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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 Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2016-06-29 05:41:56" + }, + { + "name": "symfony/yaml", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/1819adf2066880c7967df7180f4f662b6f0567ac", + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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 Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-07-17 14:02:08" + }, + { + "name": "webmozart/assert", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308", + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-08-09 15:02:57" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "~5.6 || ~7.0" + }, + "platform-dev": [] +} diff --git a/assets/php/vendor/nubs/random-name-generator/docker-compose.yml b/assets/php/vendor/nubs/random-name-generator/docker-compose.yml new file mode 100644 index 0000000..e52fe45 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/docker-compose.yml @@ -0,0 +1,8 @@ +version: "2" +services: + build: + build: + context: . + dockerfile: Dockerfile.tests + volumes: + - .:/code diff --git a/assets/php/vendor/nubs/random-name-generator/phpunit.xml b/assets/php/vendor/nubs/random-name-generator/phpunit.xml new file mode 100644 index 0000000..dc845da --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/phpunit.xml @@ -0,0 +1,10 @@ + + + ./tests + + + + src + + + diff --git a/assets/php/vendor/nubs/random-name-generator/src/AbstractGenerator.php b/assets/php/vendor/nubs/random-name-generator/src/AbstractGenerator.php new file mode 100644 index 0000000..abfae12 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/src/AbstractGenerator.php @@ -0,0 +1,19 @@ +getName(); + } +} diff --git a/assets/php/vendor/nubs/random-name-generator/src/All.php b/assets/php/vendor/nubs/random-name-generator/src/All.php new file mode 100644 index 0000000..d044c74 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/src/All.php @@ -0,0 +1,62 @@ +_generators = $generators; + $this->_randomizer = $randomizer; + } + + /** + * Constructs an All Generator using the default list of generators. + * + * @api + * @param \Cinam\Randomizer\Randomizer $randomizer The random number generator. + * @return \Nubs\RandomNameGenerator\All The constructed generator. + */ + public static function create(Randomizer $randomizer = null) + { + return new self([new Alliteration($randomizer), new Vgng($randomizer)], $randomizer); + } + + /** + * Gets a randomly generated name using the configured generators. + * + * @api + * @return string A random name. + */ + public function getName() + { + return $this->_getRandomGenerator()->getName(); + } + + /** + * Get a random generator from the list of generators. + * + * @return \Nubs\RandomNameGenerator\Generator A random generator. + */ + protected function _getRandomGenerator() + { + return $this->_randomizer ? $this->_randomizer->getArrayValue($this->_generators) : $this->_generators[array_rand($this->_generators)]; + } +} diff --git a/assets/php/vendor/nubs/random-name-generator/src/Alliteration.php b/assets/php/vendor/nubs/random-name-generator/src/Alliteration.php new file mode 100644 index 0000000..68ef3a2 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/src/Alliteration.php @@ -0,0 +1,59 @@ +_randomizer = $randomizer; + $this->_adjectives = file(__DIR__ . '/adjectives.txt', FILE_IGNORE_NEW_LINES); + $this->_nouns = file(__DIR__ . '/nouns.txt', FILE_IGNORE_NEW_LINES); + } + + /** + * Gets a randomly generated alliterative name. + * + * @api + * @return string A random alliterative name. + */ + public function getName() + { + $adjective = $this->_getRandomWord($this->_adjectives); + $noun = $this->_getRandomWord($this->_nouns, $adjective[0]); + + return ucwords("{$adjective} {$noun}"); + } + + /** + * Get a random word from the list of words, optionally filtering by starting letter. + * + * @param array $words An array of words to choose from. + * @param string $startingLetter The desired starting letter of the word. + * @return string The random word. + */ + protected function _getRandomWord(array $words, $startingLetter = null) + { + $wordsToSearch = $startingLetter === null ? $words : preg_grep("/^{$startingLetter}/", $words); + return $this->_randomizer ? $this->_randomizer->getArrayValue($wordsToSearch) : $wordsToSearch[array_rand($wordsToSearch)]; + } +} diff --git a/assets/php/vendor/nubs/random-name-generator/src/Generator.php b/assets/php/vendor/nubs/random-name-generator/src/Generator.php new file mode 100644 index 0000000..572c990 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/src/Generator.php @@ -0,0 +1,16 @@ +_randomizer = $randomizer; + $this->_definitionSets = array_map( + [$this, '_parseSection'], + $this->_getSections($this->_getFileContents()) + ); + } + + /** + * Gets a randomly generated video game name. + * + * @api + * @return string A random video game name. + */ + public function getName() + { + $similarWords = []; + $words = []; + + foreach ($this->_definitionSets as $definitionSet) { + $word = $this->_getUniqueWord($definitionSet, $similarWords); + $words[] = $word['word']; + $similarWords[] = $word['word']; + $similarWords = array_merge($similarWords, $word['similarWords']); + } + + return implode(' ', $words); + } + + /** + * Get a definition from the definitions that does not exist already. + * + * @param array $definitions The word list to pick from. + * @param array $existingWords The already-chosen words to avoid. + * @return array The definition of a previously unchosen word. + */ + protected function _getUniqueWord(array $definitions, array $existingWords) + { + $definition = $this->_randomizer ? $this->_randomizer->getArrayValue($definitions) : $definitions[array_rand($definitions)]; + + if (array_search($definition['word'], $existingWords) === false) { + return $definition; + } + + return $this->_getUniqueWord($definitions, $existingWords); + } + + /** + * Gets the file contents of the video_game_names.txt file. + * + * @return string The video_game_names.txt contents. + */ + protected function _getFileContents() + { + return file_get_contents(__DIR__ . '/video_game_names.txt'); + } + + /** + * Separates the contents into each of the word list sections. + * + * This builder operates by picking a random word from each of a consecutive + * list of word lists. These separate lists are separated by a line + * consisting of four hyphens in the file. + * + * @param string $contents The file contents. + * @return array Each section split into its own string. + */ + protected function _getSections($contents) + { + return array_map('trim', explode('----', $contents)); + } + + /** + * Parses the given section into the final definitions. + * + * @param string $section The newline-separated list of words in a section. + * @return array The parsed section into its final form. + */ + protected function _parseSection($section) + { + return array_map( + [$this, '_parseDefinition'], + $this->_getDefinitionsFromSection($section) + ); + } + + /** + * Gets the separate definitions from the given section. + * + * @param string $section The newline-separated list of words in a section. + * @return array Each word split out, but unparsed. + */ + protected function _getDefinitionsFromSection($section) + { + return array_map('trim', explode("\n", $section)); + } + + /** + * Parses a single definition into its component pieces. + * + * The format of a definition in a file is word[^similarWord|...]. + * + * @param string $definition The definition. + * @return array The formatted definition. + */ + protected function _parseDefinition($definition) + { + $word = strtok($definition, '^'); + $similarWords = array_filter(explode('|', strtok('^'))); + + return ['word' => $word, 'similarWords' => $similarWords]; + } +} diff --git a/assets/php/vendor/nubs/random-name-generator/src/adjectives.txt b/assets/php/vendor/nubs/random-name-generator/src/adjectives.txt new file mode 100644 index 0000000..f8d3247 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/src/adjectives.txt @@ -0,0 +1,233 @@ +adorable +adventurous +aggressive +agreeable +alert +alive +amused +angry +annoyed +annoying +anxious +arrogant +ashamed +attractive +average +awful +bad +beautiful +better +bewildered +black +bloody +blue +blue-eyed +blushing +bored +brainy +brave +breakable +bright +busy +calm +careful +cautious +charming +cheerful +clean +clear +clever +cloudy +clumsy +colorful +combative +comfortable +concerned +condemned +confused +cooperative +courageous +crazy +creepy +crowded +cruel +curious +cute +dangerous +dark +dead +defeated +defiant +delightful +depressed +determined +different +difficult +disgusted +distinct +disturbed +dizzy +doubtful +drab +dull +eager +easy +elated +elegant +embarrassed +enchanting +encouraging +energetic +enthusiastic +envious +evil +excited +expensive +exuberant +fair +faithful +famous +fancy +fantastic +fierce +filthy +fine +foolish +fragile +frail +frantic +friendly +frightened +funny +gentle +gifted +glamorous +gleaming +glorious +good +gorgeous +graceful +grieving +grotesque +grumpy +handsome +happy +healthy +helpful +helpless +hilarious +homeless +homely +horrible +hungry +hurt +ill +important +impossible +inexpensive +innocent +inquisitive +itchy +jealous +jittery +jolly +joyous +kind +lazy +light +lively +lonely +long +lovely +lucky +magnificent +misty +modern +motionless +muddy +mushy +mysterious +nasty +naughty +nervous +nice +nutty +obedient +obnoxious +odd +old-fashioned +open +outrageous +outstanding +panicky +perfect +plain +pleasant +poised +poor +powerful +precious +prickly +proud +puzzled +quaint +real +relieved +repulsive +rich +scary +selfish +shiny +shy +silly +sleepy +smiling +smoggy +sore +sparkling +splendid +spotless +stormy +strange +stupid +successful +super +talented +tame +tender +tense +terrible +testy +thankful +thoughtful +thoughtless +tired +tough +troubled +ugliest +ugly +uninterested +unsightly +unusual +upset +uptight +vast +victorious +vivacious +wandering +weary +wicked +wide-eyed +wild +witty +worrisome +worried +wrong +xenophobic +xanthous +xerothermic +yawning +yellowed +yucky +zany +zealous diff --git a/assets/php/vendor/nubs/random-name-generator/src/nouns.txt b/assets/php/vendor/nubs/random-name-generator/src/nouns.txt new file mode 100644 index 0000000..4e4adc2 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/src/nouns.txt @@ -0,0 +1,313 @@ +aardvark +addax +albatross +alligator +alpaca +anaconda +angelfish +anteater +antelope +ant +ape +armadillo +baboon +badger +barracuda +bat +batfish +bear +beaver +bee +beetle +bird +bison +boar +booby +buffalo +bug +butterfly +buzzard +caiman +camel +capuchin +capybara +caracal +cardinal +caribou +cassowary +cat +caterpillar +centipede +chamois +cheetah +chicken +chimpanzee +chinchilla +chipmunk +cicada +civet +cobra +cockroach +cod +constrictor +copperhead +cormorant +corncrake +cottonmouth +cowfish +cow +coyote +crab +crane +crayfish +crocodile +crossbill +curlew +deer +dingo +dog +dogfish +dolphin +donkey +dormouse +dotterel +dove +dragonfly +duck +dugong +dunlin +eagle +earthworm +echidna +eel +eland +elephant +elk +emu +falcon +ferret +finch +fish +flamingo +flatworm +fly +fowl +fox +frog +gannet +gaur +gazelle +gecko +gemsbok +gentoo +gerbil +gerenuk +gharial +gibbon +giraffe +gnat +gnu +goat +goldfinch +goosander +goose +gorilla +goshawk +grasshopper +grebe +grivet +grouse +guanaco +gull +hamerkop +hamster +hare +hawk +hedgehog +heron +herring +hippopotamus +hoopoe +hornet +horse +hummingbird +hyena +ibex +ibis +iguana +impala +jackal +jaguar +jay +jellyfish +kangaroo +katipo +kea +kestrel +kingfisher +kinkajou +kitten +koala +kookaburra +kouprey +kudu +ladybird +lapwing +lark +lemur +leopard +lion +lizard +llama +lobster +locust +loris +louse +lynx +lyrebird +macaque +macaw +magpie +mallard +mamba +manatee +mandrill +mantis +manx +markhor +marten +meerkat +millipede +mink +mockingbird +mole +mongoose +monkey +moose +mosquito +moth +mouse +narwhal +newt +nightingale +ocelot +octopus +okapi +opossum +orangutan +oryx +osprey +ostrich +otter +owl +ox +oyster +oystercatcher +panda +panther +parrot +partridge +peacock +peafowl +peccary +pelican +penguin +petrel +pheasant +pig +pigeon +pintail +piranha +platypus +plover +polecat +pollan +pony +porcupine +porpoise +puffin +puma +pygmy +quagga +quail +quelea +quetzal +quoll +rabbit +raccoon +rat +ratel +rattlesnake +raven +ray +reindeer +rhinoceros +rook +sable +salamander +salmon +sandpiper +sardine +scarab +seahorse +seal +serval +shark +sheep +shrew +shrike +skimmer +skipper +skunk +skylark +sloth +snail +snake +spider +squirrel +stag +starling +stoat +stork +swan +swiftlet +tamarin +tapir +tarantula +tarsier +teira +termite +tern +thrush +tiger +toad +tortoise +toucan +trout +tuatara +turkey +turtle +unicorn +vendace +vicuña +vole +vulture +wallaby +walrus +warbler +wasp +weasel +weevil +whale +wildebeest +willet +wolf +wolverine +wombat +worm +wren +wryneck +xenomorph +yacare +yak +zebra diff --git a/assets/php/vendor/nubs/random-name-generator/src/video_game_names.txt b/assets/php/vendor/nubs/random-name-generator/src/video_game_names.txt new file mode 100644 index 0000000..a2bcaa2 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/src/video_game_names.txt @@ -0,0 +1,1276 @@ +3D +8-Bit +A Boy and His +Action +Advanced^Advance +Adventures of the^Adventure +Aero +African^in Africa +Alcoholic +Alien +Allied +All-American +All-Night +All-Star^Star|Starring Mickey Mouse|Stars|Superstar +Almighty +Amateur +Amazing +Amazon +American +Amish +Amphibious +Ancient +Android^Cyborg +Angry +Apathetic +Aquatic +Arcane +Armored +Art of +Asian +Astral +Attack of the^Attack +Atomic^Nuclear +Australian +Awesome +Barbie's +Battle^Battleship|Battalion +Battlefield:^Battle|Battleship|Battalion +Beautiful^Beautician +Bewildering +Biblical +Big^Big Game Hunter +Big Bird's^Big Game Hunter +Big-Time^Big Game Hunter +Bionic +Bizarre +Bizarro +Black +Blasphemous +Blazing +Bling Bling +Blissful +Blocky +Bloody^Blood|Bloodbath|of the Blood God +Bonk's +Boring +Bouncin' +Brain-Damaged +British +Britney Spears' +BudgetSoft Presents: +Caesar's +Canadian +Cantankerous +Caribbean +Catholic +Celebrity +Celtic +Charlie Brown's +Children of the +Chillin' +Chinese +Chocolate +Christian +Claustrophobic +College +Colonial +Combat +Communist +Confusing +Cool +Corporate +Cosmic +Crazy +Create Your Own^Creator +Creepy +Cthulhu's +Curse of the +Custom +Cyber +Cybernetic +Cyborg^Android +Dance Dance^Breakdancing|Dance|Dance Mix|Dance Party|Dancers|Square Dancing +Dangerous +Darkest +Day of the +Dead or Alive^Death|Deathmatch|of Death|of the Dead +Deadly^Death|Deathmatch|of Death|of the Dead +Death-Defying^Death|Deathmatch|of Death|of the Dead +Deep Space^of the Deep +Def Jam +Demonic +Depressing +Deranged +Derek Smart's +Dirty +Disney's +Distinguished +Disturbing +Divine +Donkey Kong's +Double +Downtown +Dr. +Dracula's +Drug-Induced +Drunken +Duke Nukem: +Dwarven^Dwarf|Gnome|Midget +Dynamite +Ebony +Eco-Friendly +Educational +Elderly +Electric +Elegant +Elite +Elmo's +Emo +Endless +Enormous +Enraged +Epic +Erotic +Escape from the +Eternal +European +Everybody Hates the +Everybody Loves the +Exciting +Excruciating +Explosive^Explosion +Exquisite +Extreme^X-Treme +Fabulous +Fancy +Fantastic +Fantasy +Fatal +Feverish +Fiery +Final +Final Fantasy^Fantasy +First-Person +Fisher Price +Flamboyant +Fluffy +Flying +Forbidden +Forgotten +Frankenstein's +French +Frisky +Fruity +Full Metal +Funky^Funk +Furry +Future +Galactic +Generic +Geriatric +German +Ghetto +Giant +Glowing +Go Go +God of +Golden +Gothic^Goth +Grand +Great +Grimy +Guitar +Happy +Hardcore +Haunted +Hazardous +Heavy +Heavy Metal^Metal +Heinous +Helicopter +Heroic^Hero|Heroes +Hidden +Hideous +High-Speed^Speed +Hillbilly +Hindu +Hip-Hop^Hippo +History of the +Hitler's^Nazi +Ho-Hum +Holy +Horrifying +Hyper +Imperial +Impossible +In Search of +In Search of the +In the Lost Kingdom of +In Your Face +Inappropriate +Inbred +Incomprehensible +Incredible +Indian +Indiana Jones and the +Inept +Infinite +Ingenious +Insane +Intellectual +Intelligent +Intense +Interactive +International +Internet +Interstellar +Invisible +Irish +Iron +Irresistible +Irritating +Islamic +Italian +It's a Mad, Mad^Madness +Jackie Chan's +Jamaican +Japanese +Jedi +Jewish +Johnny Turbo's +John Romero's +Kabuki +Kamikaze +Kermit's +Killer +Kinect +King of^King|Kingdom +Kinky +Kirby's +Kosher +Kung-fu +Jack Thompson's +Lair of the +Latino +Lazy +Legacy of +Legend of^Legend|Legends +Legend of the^Legend|Legends +Legendary^Legend|Legends +Leisure Suit +Lethal +Little +Looney Tunes +Lord of the^Lord +Lost +Lovely^Love|of Love|Romance +Low G +Lucky +Luigi's +M.C. Escher's +Madden +Magic^of Magic|of Might and Magic +Magical^Magic|of Magic|of Might and Magic +Magnetic +Major +Manic^Mania|Maniac +Maniac^Mania +Mario's +Mary Kate and Ashley's +Master Chief's^Master +Masters of^Master +Masters of the^Master +Maximum +Mechanized +Medieval +Mega +Mega Man's +Merciless +Metal +Mexican +Michael Jackson's +Mickey's +Micro +Middle-Eastern^in the Middle East +Mighty +Mind-Bending +Miniature^Dwarf|Gnome|Midget +Miracle +Monster^Monster Truck +Monty Python's +Morbid +Morbidly Obese +Mr. +MTV's +Muppet +Musical^DJ|Music +My First +My Little +My Very Own +Mysterious^of Mystery +Mystery^of Mystery +Mystic^of Mystery +Mystical^of Mystery +Mythical +Narcoleptic +Nasty +National Lampoon's +Naughty +NBA +NCAA +Nerf +Neo +Neon +Neurotic +New +Night of the^Knights|Night|Nightmare +Nighttime^Knights|Night|Nightmare +Nihilistic +Ninja +No One Can Stop the +Nostalgic +Nuclear^Atomic +Nudist +Obsessive-Compulsive +Occult +Olympic +Omega +Orbital +Pagan +Panzer +Papal +Paranoid +Pathetic +Peaceful +Perfect +Perverted +Phoenix Wright: +Pixellated +Planet of the^Planet +Political +Post-Apocalyptic +Prehistoric +Preschool +Presidential +Primal +Pro +Profane +Professional^Pro +Psychedelic +Psycho +Queen of the^Princess +Quiet +Rad +Radical +Radioactive +Raging^Rage +Real +Red Hot +Regal +Relentless +Religious +Remote +Renegade +Retarded +Retro +Return of^Returns|Strikes Again|Strikes Back +Return of the^Returns|Strikes Again|Strikes Back +Revenge of^Revenge|- The Revenge +Revenge of the^Revenge|- The Revenge +Rise of the +Robot +Robotic^Robot +Rock 'n' Roll +Rocket-Powered +Rockin' +Rogue +Romantic +Royal +Rural +Rushing^Rush +Russian +Samba de +Samurai +Satan's +Savage +Save Yourself from the +Scandinavian +Scooby Doo and the +Scottish +Screaming +Search for the +Secret of the +Sensual^Sex +Sexy^Sex +Shadow of the^Shadow +Shady +Shameful +Shrunken^Midget +Sid Meier's +Silent +Silly +Sim^Simulator +Sinister +Sleazy +Sleepy +Small-Time +Sonic's +Soviet +Space +Special^Special Edition +Spectacular +Spectral^Ghost +Spirit of the +Spooky +Spunky +Star^All-Stars|Starring Mickey Mouse|Stars|Superstar +Star Trek^All-Stars|Star|Starring Mickey Mouse|Stars|Superstar +Star Wars^All-Stars|Star|Starring Mickey Mouse|Stars|Superstar +Stealth +Stoic +Strategic +Street +Stupendous +Stylish +Subatomic +Subterranean^Underground|Underworld +Summer +Super +Super Sexy^Sex|Superstar +Supreme +Surprise +Tactical^Tactics +Tasteless +Team +Teenage +Telekinetic +Terrible +The +The Care Bears' +The Castle of +The Castle of the +The Glory of +The Great +The Harlem Globetrotters: +The Hunt For the +The Incredible +The Infernal +The Last +The Muppets^Muppets +The Quest for the^Quest +The Secret Weapon of the +The Simpsons' +The Sims: +The Six Million Dollar +Third-World +Throbbing +Tiger Woods' +Tiny^Midget +Tom Clancy's +Tony Hawk's +Topsy-Turvy +Toxic +Transvestite +Trendy +Tribal +Tropical +True Crime: +Turbo +Twin +Twisted +Ultimate +Ultra +Ultraviolent^Ultra +Unbelievable +Undead +Undercover^Under Fire|Underwear|Underground|Underworld +Underground^Under Fire|Underwear|Underground|Underworld +Underwater^Under Fire|Underwear|Underground|Underworld +Unforgettable +Unholy +Unpleasant +Unreal +Unremarkable +Unstoppable +Urban +Vampire +Vegetarian +Viking +Violent +Virtua +Virtual +Wacky +Wandering +War of the^Warfare|Warrior|Wars|- Total War +We Love +Weary +Wild^Gone Wild +Wonderous +Wooden +World^World Cup|World Tour +World of^World|World Cup|World Tour +Wrath of the +WWII^Warfare|Warrior|Wars|World|World Cup|World Tour|- Total War +Ye Olde +Yoshi's +Zany +Zombie^Zombies +---- +3D +Acid +Aerobics +Afro +Alien +Alligator +Amish +Android +Animal +Arcade +Architecture +Army +Assault +Axe +Badminton^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Baking +Ballet +Balloon +Banana +Bandicoot +Banjo +Barbarian +Barcode +Baseball^Base|Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Basketball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Bass +Batman +Battle^Battalion +Battleship^Battle|Battalion +Bazooka +Beach +Beast +Beat +Beautician +Bedtime +Bible +Big Game Hunter^Hunt|Hunter +Bimbo +Bingo +Biplane +Blade +Blimp +Blood^Bloodbath|of the Blood God +BMX +Bobsled +Bomb +Bomberman +Bong +Bongo +Booty +Bow Hunter^Hunt|Hunter +Bowling^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Boxing^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Breakdancing^Dance Mix|Dance Party|Dancers +Bubble +Bubblegum +Buddhist +Bungie +Burger +Business +Cannibal +Car +Cardboard +Carnival +Casino +Castlevania +Catapult +Caveman^Man +Chainsaw +Chase +Cheese +Chef +Chess +Chicken +Chipmunk +Chocobo +Circus +City +College +Combat +Computer +Conga +Cookie +Cooking +Cowboy +Cricket^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Croquet^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Crowbar +Crystal +Cyborg +Dance^Dance Mix|Dance Party|Dancers +Dating +Death^Deathmatch|of Death|of the Dead +Deer Hunter +Demon +Dentist +Desert^in the Desert +Devil +Dinosaur +Disco +Dodgeball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Dog +Donkey +Dragon +Driving +Drug-Dealing +Duck +Dungeon +Dwarf +Elevator +Equestrian +Fashion +Fantasy +Farm^Farmer +Fencing +Fighter^Fight|Fight Club +Fire +Fishing^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Flatulence +Florist +Football^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Forklift +Frisbee +Frog +Fun +Fun Noodle +Funk +Furry +Ghost +Gimp +Gnome +Go-Kart +Goblin +Godzilla +Golf +Gopher +Goth +Graveyard +Grizzly Bear +Guitar +Gun +Hair Salon +Hammer +Hamster +Handgun +Hang Glider +Hardware +Harpoon +Harvest +Helicopter +Hillbilly +Hippo +Hitman^Man +Hobo +Hockey +Hoedown^Beatdown|Showdown|Smackdown|Takedown +Hovercraft +Horse Racing^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Ice +Ice Cream +Indian +Insect +Internet +Jackhammer +Janitor +Jazz +Jetpack +Jetski +Juggalo +Jungle +Kabuki +Kangaroo +Karaoke +Karate +Kart +Katana +Kitchen +Kung-fu +Lacrosse^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Landmine +Laser +Lawnmower +Lego +Leisure Suit +Lightning +Limbo +Lizard +Llama +Love^of Love|Romance +Lowrider +Mafia +Magic^of Magic|of Might and Magic +Mahjong +Makeout +Makeover +Mall +Manlove +Matador +Math +Maze +Mech +Metal +Midget +Military +Mind Control +Monkey +Monster +Monster Truck +Moon +Moped +Motorcycle +Motocross +Mountain Climber +Mummy +Mushroom +Music^DJ +Mutant +NASCAR +Nazi +Night^Knights|Nightmare +Ninja +Nuclear +Nudist +Octopus +Office +Ostrich +Outlaw +Pachinko +Paintball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Penguin +Piano +Pinball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Ping Pong^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Pirate +Platypus +Plumber +Plunger +Pogo +Pokemon +Police +Polka +Pony +Porn +Princess +Prison +Programming +Punching +Puppy +Puzzle +Quantum +Quiz +Rabbit +Raccoon +Racing^Racer +Railroad +Rainbow +River +Robot +Rocket +Rodeo +Rollerball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Rugby^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Sailboat +Sailor +Samurai +Sandwich +Scooter +Scorched Earth +Sewer +Sex +Shadow +Shark +Shaving +Shock +Shopping +Shotgun +Skate +Skydiving +Sloth +Sniper +Snowboard +Soccer +Software +Spatula +Speed +Spelling +Spelunking +Spider +Spork +Square Dancing^Dance Mix|Dance Party|Dancers +Squirrel +Stapler +STD +Stick +Stunt +Submarine +Sumo +Sudoku +Sunshine +Surf +Surgery +Sushi +Sword +Tank +Techno +Tennis +Terrorist +Tetris +Theme Park^Park +Thief +Thunder +Toon +Trailer Park^Park +Train +Trampoline +Transvestite +Tricycle +Turtle +Typing +UFO +Underwear^Under Fire|Underground|Underworld +Unicorn +Unicycle +Valkyrie +Vampire +Vegetarian +Vigilante +Viking +Vocabulary +Volleyball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo +Wagon +Walrus +Wedding +Weight Loss +Werewolf +Whale +Wheelchair +Wizard +Workout +Worm +Wrestling +Writing +WWE +WWII^Warfare|Warrior|Wars|World|World Cup|World Tour|- Total War +Yak +Yeti +Yoga +Zamboni +Zombie^Zombies +---- +- 2nd Impact +- 3rd Strike +1942 +25th Anniversary Edition +2K +2000 +3000 +3D +64 +95 +Academy +Advance +Adventure +Agent +All-Stars +Alpha +Anarchy +Annihilation +Anthology +Apocalypse +Arena +Armada +Armageddon +Assassins +Assault +at the Olympics +Attack +Babies +Bandit +Bandits +Bastards +Battle +Battalion +Base +Baseball +Basketball +Beatdown +Beta +Blast +Blaster +Bloodbath +Boxing +Boy +Brawl +Brothers +Camp +Caper +Carnage +Castle +CD +Challenge +Championship +Chase +Choreographer +Chronicles +City +Co-Op +Collection +- Collector's Edition +College +Colosseum +Combat +Commander +Commando +Competition +Conflict +Connection +Conquest +Conspiracy +Conundrum +Corps +Country +Creator +Crime Scene Investigation +Crisis +Crusade +Crusader +Dance Mix +Dance Party +Dancers +Daredevils +Dash +Deathmatch +Deluxe +Demolition +Derby +Desperadoes +Destruction +Detective +Diesel +Disaster +DJ +Domination +Dreamland +DS +Dudes +Dungeon +DX +Dynasty +Dystopia +Empire +Encounter +Enforcer +Epidemic +Espionage +EX +Exhibition +Experience +Expert +Explorer +Explosion +Express +Extra +Extravaganza +Factory +Family +Fandango +Fantasy +Farmer +Fest +Feud +Fever +Fiasco +Fiesta +Fight +Fight Club +Fighter +Football +For Kids +Force +Forever +Fortress +Freak +Frenzy +from Hell +from Mars +from Outer Space +from Planet X +Fun +Gaiden +Gang +Girl +Gold +Gone Wild +Gladiator +Groove +GT +Havoc +Hell +Hero +Heroes +Hoedown +Hop-A-Bout +Horde +Horror +Hospital +- Hot Pursuit +House +Hunt +Hunter +II +III +Ignition +in Africa +in Busytown +in Crazyland +in Middle-Earth +in My Pocket +in Space +in the Bayou +in the Dark +in the Desert +in the Hood +in the Magic Kingdom +in the Middle East +in the Outback +in the Salad Kingdom +in the Sky +in Toyland +in Vegas +Incident +Inferno +Insanity +Inspector +Insurrection +Interactive +Interceptor +Invaders +Invasion +Island +Jam +Jamboree +Jihad +Joe +Journey +Jr. +Kid +Kids +King +Kingdom +Knights +Kombat +Legend +Legends +- Limited Edition +Live +Lord +Machine +Madness +Man +Mania +Maniac +Mansion +Marines +Massacre +Master +Maxx +Mayhem +Melee +Mission +Munchers +Nation +Nightmare +Nitro +Odyssey +of Death +of Doom +of Fury +of Love +of Magic +of Might and Magic +of Mystery +of the Blood God +of the Damned +of the Dead +of the Deep +of the Third Reich +on the Oregon Trail +Offensive +Omega +on the High Seas +On The Road +on Wheels +Online +Onslaught +Operation +Operatives +Oppression +Orchestra +Over Normandy +Overdrive +Overload +Overlords +Paintbrawl +Palace +Panic +Paratroopers +Park +Party +Patrol +Phonics +Pimps +Pinball +Pioneer +Planet +Playhouse +Plus +Police +Polo +Posse +Power +Preacher +Princess +Pro +Project +Prophecy +Psychiatrist +Punch-Out!! +Punishment +Quest +Quiz +Racer +Rage +Raider +Rally +Rampage +Rangers +Ransom +Rave +Rebellion +Reloaded +Remix +Rescue +Restaurant +Returns +Revenge +Revisited +Revolution +Rider +Riders +Rocket +Romance +Romp +Roundup +Runner +Rush +Safari +Saga +Saloon +Scam +Scandal +School +Shack +Shoot +Shootout +Showdown +Siege +Simulator +Sisters +Slam +Slaughter +Slayer +Smackdown +Smash +Smuggler +Solid +Soldier +Special Edition +Spectacular +Spies +Spree +Squadron +Stadium +Starring Mickey Mouse +Stars +Story +Strike Force +Strikes Again +Strikes Back +Struggle +Studio +Summit +Summoner +Superstar +Symphony +Syndicate +Syndrome +Tactics +Takedown +Tale +Task Force +Temple +Terror +Thieves +- The Card Game +- The Dark Project +- The Gathering Storm +- The Lost Levels +- The Movie +- The Next Generation +- The Quickening +- The Resistance +- The Revenge +Through Time +Throwdown +- Total War +Tournament +Trader +Train +Trainer +Training +Tribe +Trilogy +Trivia +Troopers +Turbo +Tycoon +Ultra +Unit +Uncensored +Underground +Underworld +Universe +Unleashed +Uprising +Vengeance +Voyage +vs. Capcom +vs. Street Fighter +vs. The Space Mutants +Warfare +Warrior +Wars +Wasteland +with Friends +World +World Cup +World Tour +Wranglers +X +XP +XXX +X-treme +Yoga +Z +Zombies +Zone diff --git a/assets/php/vendor/nubs/random-name-generator/tests/AllTest.php b/assets/php/vendor/nubs/random-name-generator/tests/AllTest.php new file mode 100644 index 0000000..8049ab7 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/tests/AllTest.php @@ -0,0 +1,72 @@ + + */ +class AllTest extends PHPUnit_Framework_TestCase +{ + /** + * Verify basic behavior of getName(). + * + * @test + * @covers ::__construct + * @covers ::create + * @covers ::getName + * @uses \Nubs\RandomNameGenerator\Alliteration + * @uses \Nubs\RandomNameGenerator\Vgng + * + * @return void + */ + public function getNameBasic() + { + $generator = All::create(); + $name = $generator->getName(); + $this->assertRegexp('/.+/', $name); + } + + /** + * Verify basic behavior of getName() with a forced random generator. + * + * @test + * @covers ::__construct + * @covers ::create + * @covers ::getName + * @uses \Nubs\RandomNameGenerator\Alliteration + * + * @return void + */ + public function getNameForced() + { + $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator'); + $numberGenerator->expects($this->exactly(2))->method('getInt')->will($this->onConsecutiveCalls(20, 5)); + $randomizer = new Randomizer($numberGenerator); + + $generator = new All([new Alliteration($randomizer)]); + $this->assertSame('Black Bear', $generator->getName()); + } + + /** + * Verify basic behavior of __toString(). + * + * @test + * @covers ::__construct + * @covers ::create + * @covers ::__toString + * @covers ::getName + * @uses \Nubs\RandomNameGenerator\Alliteration + * @uses \Nubs\RandomNameGenerator\Vgng + * + * @return void + */ + public function toStringBasic() + { + $generator = All::create(); + $name = (string)$generator; + $this->assertRegexp('/.+/', $name); + } +} diff --git a/assets/php/vendor/nubs/random-name-generator/tests/AlliterationTest.php b/assets/php/vendor/nubs/random-name-generator/tests/AlliterationTest.php new file mode 100644 index 0000000..0b47343 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/tests/AlliterationTest.php @@ -0,0 +1,66 @@ + + */ +class AlliterationTest extends PHPUnit_Framework_TestCase +{ + /** + * Verify basic behavior of getName(). + * + * @test + * @covers ::__construct + * @covers ::getName + * + * @return void + */ + public function getNameBasic() + { + $generator = new Alliteration(); + $parts = explode(' ', $generator->getName()); + $this->assertSame(2, count($parts)); + $this->assertSame($parts[0][0], $parts[1][0]); + } + + /** + * Verify basic behavior of getName() with a forced random generator. + * + * @test + * @covers ::__construct + * @covers ::getName + * + * @return void + */ + public function getNameForced() + { + $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator'); + $numberGenerator->expects($this->exactly(2))->method('getInt')->will($this->onConsecutiveCalls(20, 5)); + $randomizer = new Randomizer($numberGenerator); + + $generator = new Alliteration($randomizer); + $this->assertSame('Black Bear', $generator->getName()); + } + + /** + * Verify basic behavior of __toString(). + * + * @test + * @covers ::__construct + * @covers ::__toString + * @covers ::getName + * + * @return void + */ + public function toStringBasic() + { + $generator = new Alliteration(); + $parts = explode(' ', (string)$generator); + $this->assertSame(2, count($parts)); + $this->assertSame($parts[0][0], $parts[1][0]); + } +} diff --git a/assets/php/vendor/nubs/random-name-generator/tests/VgngTest.php b/assets/php/vendor/nubs/random-name-generator/tests/VgngTest.php new file mode 100644 index 0000000..a301b81 --- /dev/null +++ b/assets/php/vendor/nubs/random-name-generator/tests/VgngTest.php @@ -0,0 +1,67 @@ + + */ +class VgngTest extends PHPUnit_Framework_TestCase +{ + /** + * Verify that getName returns the expected name. + * + * @test + * @covers ::__construct + * @covers ::getName + */ + public function getNameBasic() + { + $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator'); + $numberGenerator->expects($this->exactly(3))->method('getInt')->will($this->returnValue(1)); + $randomizer = new Randomizer($numberGenerator); + + $vgng = new Vgng($randomizer); + + $this->assertSame('8-Bit Acid - 3rd Strike', $vgng->getName()); + } + + /** + * Verify that getName returns a name without similar strings. + * + * @test + * @covers ::__construct + * @covers ::getName + */ + public function getNameSimilarName() + { + $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator'); + $numberGenerator->expects($this->exactly(4))->method('getInt')->will($this->onConsecutiveCalls(0, 0, 2, 10)); + $randomizer = new Randomizer($numberGenerator); + + $vgng = new Vgng($randomizer); + + $this->assertSame('3D Aerobics Academy', $vgng->getName()); + } + + /** + * Verify that toString returns the expected name. + * + * @test + * @covers ::__construct + * @covers ::__toString + * @covers ::getName + */ + public function toStringBasic() + { + $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator'); + $numberGenerator->expects($this->exactly(3))->method('getInt')->will($this->returnValue(1)); + $randomizer = new Randomizer($numberGenerator); + + $vgng = new Vgng($randomizer); + + $this->assertSame('8-Bit Acid - 3rd Strike', (string)$vgng); + } +} diff --git a/assets/php/vendor/paragonie/random_compat/LICENSE b/assets/php/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 0000000..45c7017 --- /dev/null +++ b/assets/php/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/assets/php/vendor/paragonie/random_compat/build-phar.sh b/assets/php/vendor/paragonie/random_compat/build-phar.sh new file mode 100755 index 0000000..b4a5ba3 --- /dev/null +++ b/assets/php/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/assets/php/vendor/paragonie/random_compat/composer.json b/assets/php/vendor/paragonie/random_compat/composer.json new file mode 100644 index 0000000..1c5978c --- /dev/null +++ b/assets/php/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/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000..eb50ebf --- /dev/null +++ b/assets/php/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/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000..6a1d7f3 --- /dev/null +++ b/assets/php/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/assets/php/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/assets/php/vendor/paragonie/random_compat/lib/byte_safe_strings.php new file mode 100644 index 0000000..3de86b2 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/byte_safe_strings.php @@ -0,0 +1,181 @@ + 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/assets/php/vendor/paragonie/random_compat/lib/cast_to_int.php b/assets/php/vendor/paragonie/random_compat/lib/cast_to_int.php new file mode 100644 index 0000000..9a4fab9 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/cast_to_int.php @@ -0,0 +1,75 @@ + 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/assets/php/vendor/paragonie/random_compat/lib/error_polyfill.php b/assets/php/vendor/paragonie/random_compat/lib/error_polyfill.php new file mode 100644 index 0000000..6a91990 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/error_polyfill.php @@ -0,0 +1,49 @@ += 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/assets/php/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php new file mode 100644 index 0000000..fc1926e --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php @@ -0,0 +1,88 @@ +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/assets/php/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php new file mode 100644 index 0000000..df5b915 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php @@ -0,0 +1,167 @@ + 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/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php new file mode 100644 index 0000000..4af1a24 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php @@ -0,0 +1,88 @@ + 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/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php new file mode 100644 index 0000000..705af52 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php @@ -0,0 +1,92 @@ + 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/assets/php/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php new file mode 100644 index 0000000..aac9c01 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php @@ -0,0 +1,77 @@ + 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/assets/php/vendor/paragonie/random_compat/other/build_phar.php b/assets/php/vendor/paragonie/random_compat/other/build_phar.php new file mode 100644 index 0000000..70ef4b2 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/other/build_phar.php @@ -0,0 +1,57 @@ +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/assets/php/vendor/paragonie/random_compat/psalm-autoload.php b/assets/php/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000..d71d1b8 --- /dev/null +++ b/assets/php/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + diff --git a/assets/php/vendor/psr/http-message/CHANGELOG.md b/assets/php/vendor/psr/http-message/CHANGELOG.md new file mode 100644 index 0000000..74b1ef9 --- /dev/null +++ b/assets/php/vendor/psr/http-message/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Updated all `@return self` annotation references in interfaces to use + `@return static`, which more closelly follows the semantics of the + specification. +- Updated the `MessageInterface::getHeaders()` return annotation to use the + value `string[][]`, indicating the format is a nested array of strings. +- Updated the `@link` annotation for `RequestInterface::withRequestTarget()` + to point to the correct section of RFC 7230. +- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation + to add the parameter name (`$uploadedFiles`). +- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()` + method to correctly reference the method parameter (it was referencing an + incorrect parameter name previously). + +## 1.0.0 - 2016-05-18 + +Initial stable release; reflects accepted PSR-7 specification. diff --git a/assets/php/vendor/psr/http-message/LICENSE b/assets/php/vendor/psr/http-message/LICENSE new file mode 100644 index 0000000..c2d8e45 --- /dev/null +++ b/assets/php/vendor/psr/http-message/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 PHP Framework Interoperability Group + +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/assets/php/vendor/psr/http-message/README.md b/assets/php/vendor/psr/http-message/README.md new file mode 100644 index 0000000..2818533 --- /dev/null +++ b/assets/php/vendor/psr/http-message/README.md @@ -0,0 +1,13 @@ +PSR Http Message +================ + +This repository holds all interfaces/classes/traits related to +[PSR-7](http://www.php-fig.org/psr/psr-7/). + +Note that this is not a HTTP message implementation of its own. It is merely an +interface that describes a HTTP message. See the specification for more details. + +Usage +----- + +We'll certainly need some stuff in here. \ No newline at end of file diff --git a/assets/php/vendor/psr/http-message/composer.json b/assets/php/vendor/psr/http-message/composer.json new file mode 100644 index 0000000..b0d2937 --- /dev/null +++ b/assets/php/vendor/psr/http-message/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/http-message", + "description": "Common interface for HTTP messages", + "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"], + "homepage": "https://github.com/php-fig/http-message", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/assets/php/vendor/psr/http-message/src/MessageInterface.php b/assets/php/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 0000000..dd46e5e --- /dev/null +++ b/assets/php/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/assets/php/vendor/psr/http-message/src/RequestInterface.php b/assets/php/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 0000000..a96d4fd --- /dev/null +++ b/assets/php/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/assets/php/vendor/psr/http-message/src/StreamInterface.php b/assets/php/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/assets/php/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/assets/php/vendor/ratchet/rfc6455/.gitignore b/assets/php/vendor/ratchet/rfc6455/.gitignore new file mode 100644 index 0000000..42ab5d5 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/.gitignore @@ -0,0 +1,4 @@ +composer.lock +vendor +tests/ab/reports +reports diff --git a/assets/php/vendor/ratchet/rfc6455/.travis.yml b/assets/php/vendor/ratchet/rfc6455/.travis.yml new file mode 100644 index 0000000..11d51b4 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/.travis.yml @@ -0,0 +1,20 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +before_install: + - export PATH=$HOME/.local/bin:$PATH + - pip install --user autobahntestsuite + - pip list --user autobahntestsuite + +before_script: + - composer install + - sh tests/ab/run_ab_tests.sh + +script: + - vendor/bin/phpunit diff --git a/assets/php/vendor/ratchet/rfc6455/LICENSE b/assets/php/vendor/ratchet/rfc6455/LICENSE new file mode 100644 index 0000000..7f8c128 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Chris Boden + +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/assets/php/vendor/ratchet/rfc6455/README.md b/assets/php/vendor/ratchet/rfc6455/README.md new file mode 100644 index 0000000..7c09148 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/README.md @@ -0,0 +1,13 @@ +# RFC6455 - The WebSocket Protocol + +[![Build Status](https://travis-ci.org/ratchetphp/RFC6455.svg?branch=master)](https://travis-ci.org/ratchetphp/RFC6455) +![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg) + +This library a protocol handler for the RFC6455 specification. +It contains components for both server and client side handshake and messaging protocol negotation. + +Aspects that are left open to interpertation in the specification are also left open in this library. +It is up to the implementation to determine how those interpertations are to be dealt with. + +This library is independent, framework agnostic, and does not deal with any I/O. +HTTP upgrade negotiation integration points are handled with PSR-7 interfaces. diff --git a/assets/php/vendor/ratchet/rfc6455/composer.json b/assets/php/vendor/ratchet/rfc6455/composer.json new file mode 100644 index 0000000..224066b --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/composer.json @@ -0,0 +1,32 @@ +{ + "name": "ratchet/rfc6455", + "type": "library", + "description": "RFC6455 WebSocket protocol handler", + "keywords": ["WebSockets", "websocket", "RFC6455"], + "homepage": "http://socketo.me", + "license": "MIT", + "authors": [{ + "name": "Chris Boden" + , "email": "cboden@gmail.com" + , "role": "Developer" + }], + "support": { + "forum": "https://groups.google.com/forum/#!forum/ratchet-php" + , "issues": "https://github.com/ratchetphp/RFC6455/issues" + , "irc": "irc://irc.freenode.org/reactphp" + }, + "autoload": { + "psr-4": { + "Ratchet\\RFC6455\\": "src" + } + }, + "require": { + "php": ">=5.4.2", + "guzzlehttp/psr7": "^1.0" + }, + "require-dev": { + "react/http": "^0.4.1", + "react/socket-client": "^0.4.3", + "phpunit/phpunit": "4.8.*" + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist b/assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist new file mode 100644 index 0000000..8f2e7d1 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist @@ -0,0 +1,27 @@ + + + + + + tests + + test/ab + + + + + + + ./src/ + + + \ No newline at end of file diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php new file mode 100644 index 0000000..70856df --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php @@ -0,0 +1,53 @@ +verifier = new ResponseVerifier; + + $this->defaultHeader = new Request('GET', '', [ + 'Connection' => 'Upgrade' + , 'Upgrade' => 'websocket' + , 'Sec-WebSocket-Version' => $this->getVersion() + , 'User-Agent' => "Ratchet" + ]); + } + + public function generateRequest(UriInterface $uri) { + return $this->defaultHeader->withUri($uri) + ->withHeader("Sec-WebSocket-Key", $this->generateKey()); + } + + public function validateResponse(RequestInterface $request, ResponseInterface $response) { + return $this->verifier->verifyAll($request, $response); + } + + public function generateKey() { + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/='; + $charRange = strlen($chars) - 1; + $key = ''; + for ($i = 0; $i < 16; $i++) { + $key .= $chars[mt_rand(0, $charRange)]; + } + + return base64_encode($key); + } + + public function getVersion() { + return 13; + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php new file mode 100644 index 0000000..c152eca --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php @@ -0,0 +1,47 @@ +verifyMethod($request->getMethod()); + $passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion()); + $passes += (int)$this->verifyRequestURI($request->getUri()->getPath()); + $passes += (int)$this->verifyHost($request->getHeader('Host')); + $passes += (int)$this->verifyUpgradeRequest($request->getHeader('Upgrade')); + $passes += (int)$this->verifyConnection($request->getHeader('Connection')); + $passes += (int)$this->verifyKey($request->getHeader('Sec-WebSocket-Key')); + $passes += (int)$this->verifyVersion($request->getHeader('Sec-WebSocket-Version')); + + return (8 === $passes); + } + + /** + * Test the HTTP method. MUST be "GET" + * @param string + * @return bool + */ + public function verifyMethod($val) { + return ('get' === strtolower($val)); + } + + /** + * Test the HTTP version passed. MUST be 1.1 or greater + * @param string|int + * @return bool + */ + public function verifyHTTPVersion($val) { + return (1.1 <= (double)$val); + } + + /** + * @param string + * @return bool + */ + public function verifyRequestURI($val) { + if ($val[0] !== '/') { + return false; + } + + if (false !== strstr($val, '#')) { + return false; + } + + if (!extension_loaded('mbstring')) { + return true; + } + + return mb_check_encoding($val, 'US-ASCII'); + } + + /** + * @param array $hostHeader + * @return bool + * @todo Once I fix HTTP::getHeaders just verify this isn't NULL or empty...or maybe need to verify it's a valid domain??? Or should it equal $_SERVER['HOST'] ? + */ + public function verifyHost(array $hostHeader) { + return (1 === count($hostHeader)); + } + + /** + * Verify the Upgrade request to WebSockets. + * @param array $upgradeHeader MUST equal "websocket" + * @return bool + */ + public function verifyUpgradeRequest(array $upgradeHeader) { + return (1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0])); + } + + /** + * Verify the Connection header + * @param array $connectionHeader MUST include "Upgrade" + * @return bool + */ + public function verifyConnection(array $connectionHeader) { + foreach ($connectionHeader as $l) { + $upgrades = array_filter( + array_map('trim', array_map('strtolower', explode(',', $l))), + function ($x) { + return 'upgrade' === $x; + } + ); + if (count($upgrades) > 0) { + return true; + } + } + return false; + } + + /** + * This function verifies the nonce is valid (64 big encoded, 16 bytes random string) + * @param array $keyHeader + * @return bool + * @todo The spec says we don't need to base64_decode - can I just check if the length is 24 and not decode? + * @todo Check the spec to see what the encoding of the key could be + */ + public function verifyKey(array $keyHeader) { + return (1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0]))); + } + + /** + * Verify the version passed matches this RFC + * @param string|int $versionHeader MUST equal 13|"13" + * @return bool + */ + public function verifyVersion($versionHeader) { + return (1 === count($versionHeader) && static::VERSION === (int)$versionHeader[0]); + } + + /** + * @todo Write logic for this method. See section 4.2.1.8 + */ + public function verifyProtocol($val) { + } + + /** + * @todo Write logic for this method. See section 4.2.1.9 + */ + public function verifyExtensions($val) { + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php new file mode 100644 index 0000000..de03f53 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php @@ -0,0 +1,52 @@ +verifyStatus($response->getStatusCode()); + $passes += (int)$this->verifyUpgrade($response->getHeader('Upgrade')); + $passes += (int)$this->verifyConnection($response->getHeader('Connection')); + $passes += (int)$this->verifySecWebSocketAccept( + $response->getHeader('Sec-WebSocket-Accept') + , $request->getHeader('Sec-WebSocket-Key') + ); + $passes += (int)$this->verifySubProtocol( + $request->getHeader('Sec-WebSocket-Protocol') + , $response->getHeader('Sec-WebSocket-Protocol') + ); + + return (5 === $passes); + } + + public function verifyStatus($status) { + return ((int)$status === 101); + } + + public function verifyUpgrade(array $upgrade) { + return (in_array('websocket', array_map('strtolower', $upgrade))); + } + + public function verifyConnection(array $connection) { + return (in_array('upgrade', array_map('strtolower', $connection))); + } + + public function verifySecWebSocketAccept($swa, $key) { + return ( + 1 === count($swa) && + 1 === count($key) && + $swa[0] === $this->sign($key[0]) + ); + } + + public function sign($key) { + return base64_encode(sha1($key . NegotiatorInterface::GUID, true)); + } + + public function verifySubProtocol(array $requestHeader, array $responseHeader) { + return 0 === count($responseHeader) || count(array_intersect($responseHeader, $requestHeader)) > 0; + } +} \ No newline at end of file diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php new file mode 100644 index 0000000..5a0073b --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php @@ -0,0 +1,136 @@ +verifier = $requestVerifier; + } + + /** + * {@inheritdoc} + */ + public function isProtocol(RequestInterface $request) { + return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version')); + } + + /** + * {@inheritdoc} + */ + public function getVersionNumber() { + return RequestVerifier::VERSION; + } + + /** + * {@inheritdoc} + */ + public function handshake(RequestInterface $request) { + if (true !== $this->verifier->verifyMethod($request->getMethod())) { + return new Response(405, ['Allow' => 'GET']); + } + + if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) { + return new Response(505); + } + + if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) { + return new Response(400); + } + + if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) { + return new Response(400); + } + + $upgradeSuggestion = [ + 'Connection' => 'Upgrade', + 'Upgrade' => 'websocket', + 'Sec-WebSocket-Version' => $this->getVersionNumber() + ]; + if (count($this->_supportedSubProtocols) > 0) { + $upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', $this->_supportedSubProtocols); + } + if (true !== $this->verifier->verifyUpgradeRequest($request->getHeader('Upgrade'))) { + return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided'); + } + + if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) { + return new Response(400, [], null, '1.1', 'Connection Upgrade MUST be requested'); + } + + if (true !== $this->verifier->verifyKey($request->getHeader('Sec-WebSocket-Key'))) { + return new Response(400, [], null, '1.1', 'Invalid Sec-WebSocket-Key'); + } + + if (true !== $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'))) { + return new Response(426, $upgradeSuggestion); + } + + $headers = []; + $subProtocols = $request->getHeader('Sec-WebSocket-Protocol'); + if (count($subProtocols) > 0 || (count($this->_supportedSubProtocols) > 0 && $this->_strictSubProtocols)) { + $subProtocols = array_map('trim', explode(',', implode(',', $subProtocols))); + + $match = array_reduce($subProtocols, function($accumulator, $protocol) { + return $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null); + }, null); + + if ($this->_strictSubProtocols && null === $match) { + return new Response(426, $upgradeSuggestion, null, '1.1', 'No Sec-WebSocket-Protocols requested supported'); + } + + if (null !== $match) { + $headers['Sec-WebSocket-Protocol'] = $match; + } + } + + return new Response(101, array_merge($headers, [ + 'Upgrade' => 'websocket' + , 'Connection' => 'Upgrade' + , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0]) + , 'X-Powered-By' => 'Ratchet' + ])); + } + + /** + * Used when doing the handshake to encode the key, verifying client/server are speaking the same language + * @param string $key + * @return string + * @internal + */ + public function sign($key) { + return base64_encode(sha1($key . static::GUID, true)); + } + + /** + * @param array $protocols + */ + function setSupportedSubProtocols(array $protocols) { + $this->_supportedSubProtocols = array_flip($protocols); + } + + /** + * If enabled and support for a subprotocol has been added handshake + * will not upgrade if a match between request and supported subprotocols + * @param boolean $enable + * @todo Consider extending this interface and moving this there. + * The spec does says the server can fail for this reason, but + * it is not a requirement. This is an implementation detail. + */ + function setStrictSubProtocolCheck($enable) { + $this->_strictSubProtocols = (boolean)$enable; + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php new file mode 100644 index 0000000..3d800e5 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php @@ -0,0 +1,24 @@ +validCloseCodes = [ + Frame::CLOSE_NORMAL, + Frame::CLOSE_GOING_AWAY, + Frame::CLOSE_PROTOCOL, + Frame::CLOSE_BAD_DATA, + Frame::CLOSE_BAD_PAYLOAD, + Frame::CLOSE_POLICY, + Frame::CLOSE_TOO_BIG, + Frame::CLOSE_MAND_EXT, + Frame::CLOSE_SRV_ERR, + ]; + } + + public function __invoke($val) { + return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php new file mode 100644 index 0000000..18aa2e3 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php @@ -0,0 +1,34 @@ + $ufExceptionFactory + */ + public function __construct($payload = null, $final = true, $opcode = 1, callable $ufExceptionFactory = null) { + $this->ufeg = $ufExceptionFactory ?: function($msg = '') { + return new \UnderflowException($msg); + }; + + if (null === $payload) { + return; + } + + $this->defPayLen = strlen($payload); + $this->firstByte = ($final ? 128 : 0) + $opcode; + $this->secondByte = $this->defPayLen; + $this->isCoalesced = true; + + $ext = ''; + if ($this->defPayLen > 65535) { + $ext = pack('NN', 0, $this->defPayLen); + $this->secondByte = 127; + } elseif ($this->defPayLen > 125) { + $ext = pack('n', $this->defPayLen); + $this->secondByte = 126; + } + + $this->data = chr($this->firstByte) . chr($this->secondByte) . $ext . $payload; + $this->bytesRecvd = 2 + strlen($ext) + $this->defPayLen; + } + + /** + * {@inheritdoc} + */ + public function isCoalesced() { + if (true === $this->isCoalesced) { + return true; + } + + try { + $payload_length = $this->getPayloadLength(); + $payload_start = $this->getPayloadStartingByte(); + } catch (\UnderflowException $e) { + return false; + } + + $this->isCoalesced = $this->bytesRecvd >= $payload_length + $payload_start; + + return $this->isCoalesced; + } + + /** + * {@inheritdoc} + */ + public function addBuffer($buf) { + $len = strlen($buf); + + $this->data .= $buf; + $this->bytesRecvd += $len; + + if ($this->firstByte === -1 && $this->bytesRecvd !== 0) { + $this->firstByte = ord($this->data[0]); + } + + if ($this->secondByte === -1 && $this->bytesRecvd >= 2) { + $this->secondByte = ord($this->data[1]); + } + } + + /** + * {@inheritdoc} + */ + public function isFinal() { + if (-1 === $this->firstByte) { + throw call_user_func($this->ufeg, 'Not enough bytes received to determine if this is the final frame in message'); + } + + return 128 === ($this->firstByte & 128); + } + + /** + * @return boolean + * @throws \UnderflowException + */ + public function getRsv1() { + if (-1 === $this->firstByte) { + throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); + } + + return 64 === ($this->firstByte & 64); + } + + /** + * @return boolean + * @throws \UnderflowException + */ + public function getRsv2() { + if (-1 === $this->firstByte) { + throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); + } + + return 32 === ($this->firstByte & 32); + } + + /** + * @return boolean + * @throws \UnderflowException + */ + public function getRsv3() { + if (-1 === $this->firstByte) { + throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); + } + + return 16 === ($this->firstByte & 16); + } + + /** + * {@inheritdoc} + */ + public function isMasked() { + if (-1 === $this->secondByte) { + throw call_user_func($this->ufeg, "Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set"); + } + + return 128 === ($this->secondByte & 128); + } + + /** + * {@inheritdoc} + */ + public function getMaskingKey() { + if (!$this->isMasked()) { + return ''; + } + + $start = 1 + $this->getNumPayloadBytes(); + + if ($this->bytesRecvd < $start + static::MASK_LENGTH) { + throw call_user_func($this->ufeg, 'Not enough data buffered to calculate the masking key'); + } + + return substr($this->data, $start, static::MASK_LENGTH); + } + + /** + * Create a 4 byte masking key + * @return string + */ + public function generateMaskingKey() { + $mask = ''; + + for ($i = 1; $i <= static::MASK_LENGTH; $i++) { + $mask .= chr(rand(32, 126)); + } + + return $mask; + } + + /** + * Apply a mask to the payload + * @param string|null If NULL is passed a masking key will be generated + * @throws \OutOfBoundsException + * @throws \InvalidArgumentException If there is an issue with the given masking key + * @return Frame + */ + public function maskPayload($maskingKey = null) { + if (null === $maskingKey) { + $maskingKey = $this->generateMaskingKey(); + } + + if (static::MASK_LENGTH !== strlen($maskingKey)) { + throw new \InvalidArgumentException("Masking key must be " . static::MASK_LENGTH ." characters"); + } + + if (extension_loaded('mbstring') && true !== mb_check_encoding($maskingKey, 'US-ASCII')) { + throw new \OutOfBoundsException("Masking key MUST be ASCII"); + } + + $this->unMaskPayload(); + + $this->secondByte = $this->secondByte | 128; + $this->data[1] = chr($this->secondByte); + + $this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0); + + $this->bytesRecvd += static::MASK_LENGTH; + $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); + + return $this; + } + + /** + * Remove a mask from the payload + * @throws \UnderFlowException If the frame is not coalesced + * @return Frame + */ + public function unMaskPayload() { + if (!$this->isCoalesced()) { + throw call_user_func($this->ufeg, 'Frame must be coalesced before applying mask'); + } + + if (!$this->isMasked()) { + return $this; + } + + $maskingKey = $this->getMaskingKey(); + + $this->secondByte = $this->secondByte & ~128; + $this->data[1] = chr($this->secondByte); + + $this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH); + + $this->bytesRecvd -= static::MASK_LENGTH; + $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); + + return $this; + } + + /** + * Apply a mask to a string or the payload of the instance + * @param string $maskingKey The 4 character masking key to be applied + * @param string|null $payload A string to mask or null to use the payload + * @throws \UnderflowException If using the payload but enough hasn't been buffered + * @return string The masked string + */ + public function applyMask($maskingKey, $payload = null) { + if (null === $payload) { + if (!$this->isCoalesced()) { + throw call_user_func($this->ufeg, 'Frame must be coalesced to apply a mask'); + } + + $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); + } + + $len = strlen($payload); + + if (0 === $len) { + return ''; + } + + return $payload ^ str_pad('', $len, $maskingKey, STR_PAD_RIGHT); + + // TODO: Remove this before publish - keeping methods here to compare performance (above is faster but need control against v0.3.3) + + $applied = ''; + for ($i = 0, $len = strlen($payload); $i < $len; $i++) { + $applied .= $payload[$i] ^ $maskingKey[$i % static::MASK_LENGTH]; + } + + return $applied; + } + + /** + * {@inheritdoc} + */ + public function getOpcode() { + if (-1 === $this->firstByte) { + throw call_user_func($this->ufeg, 'Not enough bytes received to determine opcode'); + } + + return ($this->firstByte & ~240); + } + + /** + * Gets the decimal value of bits 9 (10th) through 15 inclusive + * @return int + * @throws \UnderflowException If the buffer doesn't have enough data to determine this + */ + protected function getFirstPayloadVal() { + if (-1 === $this->secondByte) { + throw call_user_func($this->ufeg, 'Not enough bytes received'); + } + + return $this->secondByte & 127; + } + + /** + * @return int (7|23|71) Number of bits defined for the payload length in the fame + * @throws \UnderflowException + */ + protected function getNumPayloadBits() { + if (-1 === $this->secondByte) { + throw call_user_func($this->ufeg, 'Not enough bytes received'); + } + + // By default 7 bits are used to describe the payload length + // These are bits 9 (10th) through 15 inclusive + $bits = 7; + + // Get the value of those bits + $check = $this->getFirstPayloadVal(); + + // If the value is 126 the 7 bits plus the next 16 are used to describe the payload length + if ($check >= 126) { + $bits += 16; + } + + // If the value of the initial payload length are is 127 an additional 48 bits are used to describe length + // Note: The documentation specifies the length is to be 63 bits, but I think that's a typo and is 64 (16+48) + if ($check === 127) { + $bits += 48; + } + + return $bits; + } + + /** + * This just returns the number of bytes used in the frame to describe the payload length (as opposed to # of bits) + * @see getNumPayloadBits + */ + protected function getNumPayloadBytes() { + return (1 + $this->getNumPayloadBits()) / 8; + } + + /** + * {@inheritdoc} + */ + public function getPayloadLength() { + if ($this->defPayLen !== -1) { + return $this->defPayLen; + } + + $this->defPayLen = $this->getFirstPayloadVal(); + if ($this->defPayLen <= 125) { + return $this->getPayloadLength(); + } + + $byte_length = $this->getNumPayloadBytes(); + if ($this->bytesRecvd < 1 + $byte_length) { + $this->defPayLen = -1; + throw call_user_func($this->ufeg, 'Not enough data buffered to determine payload length'); + } + + $len = 0; + for ($i = 2; $i <= $byte_length; $i++) { + $len <<= 8; + $len += ord($this->data[$i]); + } + + $this->defPayLen = $len; + + return $this->getPayloadLength(); + } + + /** + * {@inheritdoc} + */ + public function getPayloadStartingByte() { + return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); + } + + /** + * {@inheritdoc} + * @todo Consider not checking mask, always returning the payload, masked or not + */ + public function getPayload() { + if (!$this->isCoalesced()) { + throw call_user_func($this->ufeg, 'Can not return partial message'); + } + + return $this->__toString(); + } + + /** + * Get the raw contents of the frame + * @todo This is untested, make sure the substr is right - trying to return the frame w/o the overflow + */ + public function getContents() { + return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); + } + + public function __toString() { + $payload = (string)substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); + + if ($this->isMasked()) { + $payload = $this->applyMask($this->getMaskingKey(), $payload); + } + + return $payload; + } + + /** + * Sometimes clients will concatenate more than one frame over the wire + * This method will take the extra bytes off the end and return them + * @return string + */ + public function extractOverflow() { + if ($this->isCoalesced()) { + $endPoint = $this->getPayloadLength(); + $endPoint += $this->getPayloadStartingByte(); + + if ($this->bytesRecvd > $endPoint) { + $overflow = substr($this->data, $endPoint); + $this->data = substr($this->data, 0, $endPoint); + + return $overflow; + } + } + + return ''; + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php new file mode 100644 index 0000000..dc24091 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php @@ -0,0 +1,38 @@ +_frames = new \SplDoublyLinkedList; + } + + public function getIterator() { + return $this->_frames; + } + + /** + * {@inheritdoc} + */ + public function count() { + return count($this->_frames); + } + + /** + * {@inheritdoc} + */ + public function isCoalesced() { + if (count($this->_frames) == 0) { + return false; + } + + $last = $this->_frames->top(); + + return ($last->isCoalesced() && $last->isFinal()); + } + + /** + * {@inheritdoc} + */ + public function addFrame(FrameInterface $fragment) { + $this->_frames->push($fragment); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOpcode() { + if (count($this->_frames) == 0) { + throw new \UnderflowException('No frames have been added to this message'); + } + + return $this->_frames->bottom()->getOpcode(); + } + + /** + * {@inheritdoc} + */ + public function getPayloadLength() { + $len = 0; + + foreach ($this->_frames as $frame) { + try { + $len += $frame->getPayloadLength(); + } catch (\UnderflowException $e) { + // Not an error, want the current amount buffered + } + } + + return $len; + } + + /** + * {@inheritdoc} + */ + public function getPayload() { + if (!$this->isCoalesced()) { + throw new \UnderflowException('Message has not been put back together yet'); + } + + return $this->__toString(); + } + + /** + * {@inheritdoc} + */ + public function getContents() { + if (!$this->isCoalesced()) { + throw new \UnderflowException("Message has not been put back together yet"); + } + + $buffer = ''; + + foreach ($this->_frames as $frame) { + $buffer .= $frame->getContents(); + } + + return $buffer; + } + + public function __toString() { + $buffer = ''; + + foreach ($this->_frames as $frame) { + $buffer .= $frame->getPayload(); + } + + return $buffer; + } + + /** + * @return boolean + */ + public function isBinary() { + if ($this->_frames->isEmpty()) { + throw new \UnderflowException('Not enough data has been received to determine if message is binary'); + } + + return Frame::OP_BINARY === $this->_frames->bottom()->getOpcode(); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php new file mode 100644 index 0000000..07ff4f1 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php @@ -0,0 +1,231 @@ +closeFrameChecker = $frameChecker; + $this->checkForMask = (bool)$expectMask; + + $this->exceptionFactory ?: $this->exceptionFactory = function($msg) { + return new \UnderflowException($msg); + }; + + $this->onMessage = $onMessage; + $this->onControl = $onControl ?: function() {}; + } + + public function onData($data) { + while (strlen($data) > 0) { + $data = $this->processData($data); + } + } + + /** + * @param string $data + * @return null + */ + private function processData($data) { + $this->messageBuffer ?: $this->messageBuffer = $this->newMessage(); + $this->frameBuffer ?: $this->frameBuffer = $this->newFrame(); + + $this->frameBuffer->addBuffer($data); + if (!$this->frameBuffer->isCoalesced()) { + return ''; + } + + $onMessage = $this->onMessage; + $onControl = $this->onControl; + + $this->frameBuffer = $this->frameCheck($this->frameBuffer); + + $overflow = $this->frameBuffer->extractOverflow(); + $this->frameBuffer->unMaskPayload(); + + $opcode = $this->frameBuffer->getOpcode(); + + if ($opcode > 2) { + $onControl($this->frameBuffer); + + if (Frame::OP_CLOSE === $opcode) { + return ''; + } + } else { + $this->messageBuffer->addFrame($this->frameBuffer); + } + + $this->frameBuffer = null; + + if ($this->messageBuffer->isCoalesced()) { + $msgCheck = $this->checkMessage($this->messageBuffer); + if (true !== $msgCheck) { + $onControl($this->newCloseFrame($msgCheck, 'Ratchet detected an invalid UTF-8 payload')); + } else { + $onMessage($this->messageBuffer); + } + + $this->messageBuffer = null; + } + + return $overflow; + } + + /** + * Check a frame to be added to the current message buffer + * @param \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface $frame + * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface + */ + public function frameCheck(FrameInterface $frame) { + if (false !== $frame->getRsv1() || + false !== $frame->getRsv2() || + false !== $frame->getRsv3() + ) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid reserve code'); + } + + if ($this->checkForMask && !$frame->isMasked()) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an incorrect frame mask'); + } + + $opcode = $frame->getOpcode(); + + if ($opcode > 2) { + if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected a mismatch between final bit and indicated payload length'); + } + + switch ($opcode) { + case Frame::OP_CLOSE: + $closeCode = 0; + + $bin = $frame->getPayload(); + + if (empty($bin)) { + return $this->newCloseFrame(Frame::CLOSE_NORMAL); + } + + if (strlen($bin) === 1) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid close code'); + } + + if (strlen($bin) >= 2) { + list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2))); + } + + $checker = $this->closeFrameChecker; + if (!$checker($closeCode)) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid close code'); + } + + if (!$this->checkUtf8(substr($bin, 2))) { + return $this->newCloseFrame(Frame::CLOSE_BAD_PAYLOAD, 'Ratchet detected an invalid UTF-8 payload in the close reason'); + } + + return $frame; + break; + case Frame::OP_PING: + case Frame::OP_PONG: + break; + default: + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid OP code'); + break; + } + + return $frame; + } + + if (Frame::OP_CONTINUE === $frame->getOpcode() && 0 === count($this->messageBuffer)) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected the first frame of a message was a continue'); + } + + if (count($this->messageBuffer) > 0 && Frame::OP_CONTINUE !== $frame->getOpcode()) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected invalid OP code when expecting continue frame'); + } + + return $frame; + } + + /** + * Determine if a message is valid + * @param \Ratchet\RFC6455\Messaging\MessageInterface + * @return bool|int true if valid - false if incomplete - int of recommended close code + */ + public function checkMessage(MessageInterface $message) { + if (!$message->isBinary()) { + if (!$this->checkUtf8($message->getPayload())) { + return Frame::CLOSE_BAD_PAYLOAD; + } + } + + return true; + } + + private function checkUtf8($string) { + if (extension_loaded('mbstring')) { + return mb_check_encoding($string, 'UTF-8'); + } + + return preg_match('//u', $string); + } + + /** + * @return \Ratchet\RFC6455\Messaging\MessageInterface + */ + public function newMessage() { + return new Message; + } + + /** + * @param string|null $payload + * @param bool|null $final + * @param int|null $opcode + * @return \Ratchet\RFC6455\Messaging\FrameInterface + */ + public function newFrame($payload = null, $final = null, $opcode = null) { + return new Frame($payload, $final, $opcode, $this->exceptionFactory); + } + + public function newCloseFrame($code, $reason = '') { + return $this->newFrame(pack('n', $code) . $reason, true, Frame::OP_CLOSE); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php new file mode 100644 index 0000000..fd7212e --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php @@ -0,0 +1,20 @@ +markTestSkipped('Autobahn TestSuite results not found'); + } + + $resultsJson = file_get_contents($fileName); + $results = json_decode($resultsJson); + $agentName = array_keys(get_object_vars($results))[0]; + + foreach ($results->$agentName as $name => $result) { + if ($result->behavior === "INFORMATIONAL") { + continue; + } + + $this->assertTrue(in_array($result->behavior, ["OK", "NON-STRICT"]), "Autobahn test case " . $name . " in " . $fileName); + } + } + + public function testAutobahnClientResults() { + $this->verifyAutobahnResults(__DIR__ . '/ab/reports/clients/index.json'); + } + + public function testAutobahnServerResults() { + $this->verifyAutobahnResults(__DIR__ . '/ab/reports/servers/index.json'); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/clientRunner.php b/assets/php/vendor/ratchet/rfc6455/tests/ab/clientRunner.php new file mode 100644 index 0000000..0c5578a --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/clientRunner.php @@ -0,0 +1,228 @@ +createCached('8.8.8.8', $loop); + +$factory = new \React\SocketClient\Connector($loop, $dnsResolver); + +function echoStreamerFactory($conn) +{ + return new \Ratchet\RFC6455\Messaging\MessageBuffer( + new \Ratchet\RFC6455\Messaging\CloseFrameChecker, + function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) { + /** @var Frame $frame */ + foreach ($msg as $frame) { + $frame->maskPayload(); + } + $conn->write($msg->getContents()); + }, + function (\Ratchet\RFC6455\Messaging\FrameInterface $frame) use ($conn) { + switch ($frame->getOpcode()) { + case Frame::OP_PING: + return $conn->write((new Frame($frame->getPayload(), true, Frame::OP_PONG))->maskPayload()->getContents()); + break; + case Frame::OP_CLOSE: + return $conn->end((new Frame($frame->getPayload(), true, Frame::OP_CLOSE))->maskPayload()->getContents()); + break; + } + }, + false + ); +} + +function getTestCases() { + global $factory; + global $testServer; + + $deferred = new Deferred(); + + $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) { + $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(); + $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001/getCaseCount')); + + $rawResponse = ""; + $response = null; + + /** @var \Ratchet\RFC6455\Messaging\Streaming\MessageBuffer $ms */ + $ms = null; + + $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { + if ($response === null) { + $rawResponse .= $data; + $pos = strpos($rawResponse, "\r\n\r\n"); + if ($pos) { + $data = substr($rawResponse, $pos + 4); + $rawResponse = substr($rawResponse, 0, $pos + 4); + $response = \GuzzleHttp\Psr7\parse_response($rawResponse); + + if (!$cn->validateResponse($cnRequest, $response)) { + $stream->end(); + $deferred->reject(); + } else { + $ms = new \Ratchet\RFC6455\Messaging\MessageBuffer( + new \Ratchet\RFC6455\Messaging\CloseFrameChecker, + function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($deferred, $stream) { + $deferred->resolve($msg->getPayload()); + $stream->close(); + }, + null, + false + ); + } + } + } + + // feed the message streamer + if ($ms) { + $ms->onData($data); + } + }); + + $stream->write(\GuzzleHttp\Psr7\str($cnRequest)); + }); + + return $deferred->promise(); +} + +function runTest($case) +{ + global $factory; + global $testServer; + + $casePath = "/runCase?case={$case}&agent=" . AGENT; + + $deferred = new Deferred(); + + $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) { + $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(); + $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath)); + + $rawResponse = ""; + $response = null; + + $ms = null; + + $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { + if ($response === null) { + $rawResponse .= $data; + $pos = strpos($rawResponse, "\r\n\r\n"); + if ($pos) { + $data = substr($rawResponse, $pos + 4); + $rawResponse = substr($rawResponse, 0, $pos + 4); + $response = \GuzzleHttp\Psr7\parse_response($rawResponse); + + if (!$cn->validateResponse($cnRequest, $response)) { + $stream->end(); + $deferred->reject(); + } else { + $ms = echoStreamerFactory($stream); + } + } + } + + // feed the message streamer + if ($ms) { + $ms->onData($data); + } + }); + + $stream->on('close', function () use ($deferred) { + $deferred->resolve(); + }); + + $stream->write(\GuzzleHttp\Psr7\str($cnRequest)); + }); + + return $deferred->promise(); +} + +function createReport() { + global $factory; + global $testServer; + + $deferred = new Deferred(); + + $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) { + $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true"; + $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator(); + $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $reportPath)); + + $rawResponse = ""; + $response = null; + + /** @var \Ratchet\RFC6455\Messaging\MessageBuffer $ms */ + $ms = null; + + $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) { + if ($response === null) { + $rawResponse .= $data; + $pos = strpos($rawResponse, "\r\n\r\n"); + if ($pos) { + $data = substr($rawResponse, $pos + 4); + $rawResponse = substr($rawResponse, 0, $pos + 4); + $response = \GuzzleHttp\Psr7\parse_response($rawResponse); + + if (!$cn->validateResponse($cnRequest, $response)) { + $stream->end(); + $deferred->reject(); + } else { + $ms = new \Ratchet\RFC6455\Messaging\MessageBuffer( + new \Ratchet\RFC6455\Messaging\CloseFrameChecker, + function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($deferred, $stream) { + $deferred->resolve($msg->getPayload()); + $stream->close(); + }, + null, + false + ); + } + } + } + + // feed the message streamer + if ($ms) { + $ms->onData($data); + } + }); + + $stream->write(\GuzzleHttp\Psr7\str($cnRequest)); + }); + + return $deferred->promise(); +} + + +$testPromises = []; + +getTestCases()->then(function ($count) use ($loop) { + $allDeferred = new Deferred(); + + $runNextCase = function () use (&$i, &$runNextCase, $count, $allDeferred) { + $i++; + if ($i > $count) { + $allDeferred->resolve(); + return; + } + runTest($i)->then($runNextCase); + }; + + $i = 0; + $runNextCase(); + + $allDeferred->promise()->then(function () { + createReport(); + }); +}); + +$loop->run(); diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingclient.json b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingclient.json new file mode 100644 index 0000000..d2fd0d0 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingclient.json @@ -0,0 +1,14 @@ +{ + "options": { + "failByDrop": false + } + , "outdir": "./reports/servers" + , "servers": [{ + "agent": "RatchetRFC/0.1.0" + , "url": "ws://localhost:9001" + , "options": {"version": 18} + }] + , "cases": ["*"] + , "exclude-cases": ["6.4.*", "12.*","13.*"] + , "exclude-agent-cases": {} +} diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingserver.json b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingserver.json new file mode 100644 index 0000000..0422560 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingserver.json @@ -0,0 +1,10 @@ +{ + "url": "ws://127.0.0.1:9001" + , "options": { + "failByDrop": false + } + , "outdir": "./reports/clients" + , "cases": ["*"] + , "exclude-cases": ["6.4.*", "12.*", "13.*"] + , "exclude-agent-cases": {} +} diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/run_ab_tests.sh b/assets/php/vendor/ratchet/rfc6455/tests/ab/run_ab_tests.sh new file mode 100644 index 0000000..8fa9ced --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/run_ab_tests.sh @@ -0,0 +1,11 @@ +cd tests/ab + +wstest -m fuzzingserver -s fuzzingserver.json & +sleep 5 +php clientRunner.php + +sleep 2 + +php startServer.php & +sleep 3 +wstest -m fuzzingclient -s fuzzingclient.json diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/startServer.php b/assets/php/vendor/ratchet/rfc6455/tests/ab/startServer.php new file mode 100644 index 0000000..b256ec2 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/startServer.php @@ -0,0 +1,55 @@ +on('request', function (\React\Http\Request $request, \React\Http\Response $response) use ($negotiator, $closeFrameChecker, $uException) { + $psrRequest = new \GuzzleHttp\Psr7\Request($request->getMethod(), $request->getPath(), $request->getHeaders()); + + $negotiatorResponse = $negotiator->handshake($psrRequest); + + $response->writeHead( + $negotiatorResponse->getStatusCode(), + array_merge( + $negotiatorResponse->getHeaders(), + ["Content-Length" => "0"] + ) + ); + + if ($negotiatorResponse->getStatusCode() !== 101) { + $response->end(); + return; + } + + $parser = new \Ratchet\RFC6455\Messaging\MessageBuffer($closeFrameChecker, function(MessageInterface $message) use ($response) { + $response->write($message->getContents()); + }, function(FrameInterface $frame) use ($response, &$parser) { + switch ($frame->getOpCode()) { + case Frame::OP_CLOSE: + $response->end($frame->getContents()); + break; + case Frame::OP_PING: + $response->write($parser->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents()); + break; + } + }, true, function() use ($uException) { + return $uException; + }); + + $request->on('data', [$parser, 'onData']); +}); + +$socket->listen(9001, '0.0.0.0'); +$loop->run(); diff --git a/assets/php/vendor/ratchet/rfc6455/tests/bootstrap.php b/assets/php/vendor/ratchet/rfc6455/tests/bootstrap.php new file mode 100644 index 0000000..511b041 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/bootstrap.php @@ -0,0 +1,19 @@ +addPsr4('Ratchet\\RFC6455\\Test\\', __DIR__); + break; + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/RequestVerifierTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/RequestVerifierTest.php new file mode 100644 index 0000000..239de33 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/RequestVerifierTest.php @@ -0,0 +1,177 @@ +_v = new RequestVerifier(); + } + + public static function methodProvider() { + return array( + array(true, 'GET'), + array(true, 'get'), + array(true, 'Get'), + array(false, 'POST'), + array(false, 'DELETE'), + array(false, 'PUT'), + array(false, 'PATCH') + ); + } + /** + * @dataProvider methodProvider + */ + public function testMethodMustBeGet($result, $in) { + $this->assertEquals($result, $this->_v->verifyMethod($in)); + } + + public static function httpVersionProvider() { + return array( + array(true, 1.1), + array(true, '1.1'), + array(true, 1.2), + array(true, '1.2'), + array(true, 2), + array(true, '2'), + array(true, '2.0'), + array(false, '1.0'), + array(false, 1), + array(false, '0.9'), + array(false, ''), + array(false, 'hello') + ); + } + + /** + * @dataProvider httpVersionProvider + */ + public function testHttpVersionIsAtLeast1Point1($expected, $in) { + $this->assertEquals($expected, $this->_v->verifyHTTPVersion($in)); + } + + public static function uRIProvider() { + return array( + array(true, '/chat'), + array(true, '/hello/world?key=val'), + array(false, '/chat#bad'), + array(false, 'nope'), + array(false, '/ ಠ_ಠ '), + array(false, '/✖') + ); + } + + /** + * @dataProvider URIProvider + */ + public function testRequestUri($expected, $in) { + $this->assertEquals($expected, $this->_v->verifyRequestURI($in)); + } + + public static function hostProvider() { + return array( + array(true, ['server.example.com']), + array(false, []) + ); + } + + /** + * @dataProvider HostProvider + */ + public function testVerifyHostIsSet($expected, $in) { + $this->assertEquals($expected, $this->_v->verifyHost($in)); + } + + public static function upgradeProvider() { + return array( + array(true, ['websocket']), + array(true, ['Websocket']), + array(true, ['webSocket']), + array(false, []), + array(false, ['']) + ); + } + + /** + * @dataProvider upgradeProvider + */ + public function testVerifyUpgradeIsWebSocket($expected, $val) { + $this->assertEquals($expected, $this->_v->verifyUpgradeRequest($val)); + } + + public static function connectionProvider() { + return array( + array(true, ['Upgrade']), + array(true, ['upgrade']), + array(true, ['keep-alive', 'Upgrade']), + array(true, ['Upgrade', 'keep-alive']), + array(true, ['keep-alive', 'Upgrade', 'something']), + // as seen in Firefox 47.0.1 - see https://github.com/ratchetphp/RFC6455/issues/14 + array(true, ['keep-alive, Upgrade']), + array(true, ['Upgrade, keep-alive']), + array(true, ['keep-alive, Upgrade, something']), + array(true, ['keep-alive, Upgrade', 'something']), + array(false, ['']), + array(false, []) + ); + } + + /** + * @dataProvider connectionProvider + */ + public function testConnectionHeaderVerification($expected, $val) { + $this->assertEquals($expected, $this->_v->verifyConnection($val)); + } + + public static function keyProvider() { + return array( + array(true, ['hkfa1L7uwN6DCo4IS3iWAw==']), + array(true, ['765vVoQpKSGJwPzJIMM2GA==']), + array(true, ['AQIDBAUGBwgJCgsMDQ4PEC==']), + array(true, ['axa2B/Yz2CdpfQAY2Q5P7w==']), + array(false, [0]), + array(false, ['Hello World']), + array(false, ['1234567890123456']), + array(false, ['123456789012345678901234']), + array(true, [base64_encode('UTF8allthngs+✓')]), + array(true, ['dGhlIHNhbXBsZSBub25jZQ==']), + array(false, []), + array(false, ['dGhlIHNhbXBsZSBub25jZQ==', 'Some other value']), + array(false, ['Some other value', 'dGhlIHNhbXBsZSBub25jZQ==']) + ); + } + + /** + * @dataProvider keyProvider + */ + public function testKeyIsBase64Encoded16BitNonce($expected, $val) { + $this->assertEquals($expected, $this->_v->verifyKey($val)); + } + + public static function versionProvider() { + return array( + array(true, [13]), + array(true, ['13']), + array(false, [12]), + array(false, [14]), + array(false, ['14']), + array(false, ['hi']), + array(false, ['']), + array(false, []) + ); + } + + /** + * @dataProvider versionProvider + */ + public function testVersionEquals13($expected, $in) { + $this->assertEquals($expected, $this->_v->verifyVersion($in)); + } +} \ No newline at end of file diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ResponseVerifierTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ResponseVerifierTest.php new file mode 100644 index 0000000..312930e --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ResponseVerifierTest.php @@ -0,0 +1,34 @@ +_v = new ResponseVerifier; + } + + public static function subProtocolsProvider() { + return [ + [true, ['a'], ['a']] + , [true, ['b', 'a'], ['c', 'd', 'a']] + , [false, ['a', 'b', 'c'], ['d']] + , [true, [], []] + , [true, ['a', 'b'], []] + ]; + } + + /** + * @dataProvider subProtocolsProvider + */ + public function testVerifySubProtocol($expected, $response, $request) { + $this->assertEquals($expected, $this->_v->verifySubProtocol($response, $request)); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ServerNegotiatorTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ServerNegotiatorTest.php new file mode 100644 index 0000000..9c9aa8d --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ServerNegotiatorTest.php @@ -0,0 +1,175 @@ +handshake($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertEquals(426, $response->getStatusCode()); + $this->assertEquals('Upgrade header MUST be provided', $response->getReasonPhrase()); + $this->assertEquals('Upgrade', $response->getHeaderLine('Connection')); + $this->assertEquals('websocket', $response->getHeaderLine('Upgrade')); + $this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version')); + } + + public function testNoConnectionUpgradeRequested() { + $negotiator = new ServerNegotiator(new RequestVerifier()); + + $requestText = 'GET / HTTP/1.1 +Host: 127.0.0.1:6789 +Connection: keep-alive +Pragma: no-cache +Cache-Control: no-cache +Upgrade: websocket +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Accept-Encoding: gzip, deflate, sdch, br +Accept-Language: en-US,en;q=0.8'; + + $request = \GuzzleHttp\Psr7\parse_request($requestText); + + $response = $negotiator->handshake($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals('Connection Upgrade MUST be requested', $response->getReasonPhrase()); + } + + public function testInvalidSecWebsocketKey() { + $negotiator = new ServerNegotiator(new RequestVerifier()); + + $requestText = 'GET / HTTP/1.1 +Host: 127.0.0.1:6789 +Connection: Upgrade +Pragma: no-cache +Cache-Control: no-cache +Upgrade: websocket +Sec-WebSocket-Key: 12345 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Accept-Encoding: gzip, deflate, sdch, br +Accept-Language: en-US,en;q=0.8'; + + $request = \GuzzleHttp\Psr7\parse_request($requestText); + + $response = $negotiator->handshake($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals('Invalid Sec-WebSocket-Key', $response->getReasonPhrase()); + } + + public function testInvalidSecWebsocketVersion() { + $negotiator = new ServerNegotiator(new RequestVerifier()); + + $requestText = 'GET / HTTP/1.1 +Host: 127.0.0.1:6789 +Connection: Upgrade +Pragma: no-cache +Cache-Control: no-cache +Upgrade: websocket +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Accept-Encoding: gzip, deflate, sdch, br +Accept-Language: en-US,en;q=0.8'; + + $request = \GuzzleHttp\Psr7\parse_request($requestText); + + $response = $negotiator->handshake($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertEquals(426, $response->getStatusCode()); + $this->assertEquals('Upgrade Required', $response->getReasonPhrase()); + $this->assertEquals('Upgrade', $response->getHeaderLine('Connection')); + $this->assertEquals('websocket', $response->getHeaderLine('Upgrade')); + $this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version')); + } + + public function testBadSubprotocolResponse() { + $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator->setStrictSubProtocolCheck(true); + $negotiator->setSupportedSubProtocols([]); + + $requestText = 'GET / HTTP/1.1 +Host: 127.0.0.1:6789 +Connection: Upgrade +Pragma: no-cache +Cache-Control: no-cache +Upgrade: websocket +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Sec-WebSocket-Version: 13 +Sec-WebSocket-Protocol: someprotocol +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Accept-Encoding: gzip, deflate, sdch, br +Accept-Language: en-US,en;q=0.8'; + + $request = \GuzzleHttp\Psr7\parse_request($requestText); + + $response = $negotiator->handshake($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertEquals(426, $response->getStatusCode()); + $this->assertEquals('No Sec-WebSocket-Protocols requested supported', $response->getReasonPhrase()); + $this->assertEquals('Upgrade', $response->getHeaderLine('Connection')); + $this->assertEquals('websocket', $response->getHeaderLine('Upgrade')); + $this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version')); + } + + public function testNonStrictSubprotocolDoesNotIncludeHeaderWhenNoneAgreedOn() { + $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator->setStrictSubProtocolCheck(false); + $negotiator->setSupportedSubProtocols(['someproto']); + + $requestText = 'GET / HTTP/1.1 +Host: 127.0.0.1:6789 +Connection: Upgrade +Pragma: no-cache +Cache-Control: no-cache +Upgrade: websocket +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Sec-WebSocket-Version: 13 +Sec-WebSocket-Protocol: someotherproto +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Accept-Encoding: gzip, deflate, sdch, br +Accept-Language: en-US,en;q=0.8'; + + $request = \GuzzleHttp\Psr7\parse_request($requestText); + + $response = $negotiator->handshake($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertEquals(101, $response->getStatusCode()); + $this->assertEquals('Upgrade', $response->getHeaderLine('Connection')); + $this->assertEquals('websocket', $response->getHeaderLine('Upgrade')); + $this->assertFalse($response->hasHeader('Sec-WebSocket-Protocol')); + } +} \ No newline at end of file diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/FrameTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/FrameTest.php new file mode 100644 index 0000000..b73f600 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/FrameTest.php @@ -0,0 +1,501 @@ +_frame = new Frame; + } + + /** + * Encode the fake binary string to send over the wire + * @param string of 1's and 0's + * @return string + */ + public static function encode($in) { + if (strlen($in) > 8) { + $out = ''; + while (strlen($in) >= 8) { + $out .= static::encode(substr($in, 0, 8)); + $in = substr($in, 8); + } + return $out; + } + return chr(bindec($in)); + } + + /** + * This is a data provider + * param string The UTF8 message + * param string The WebSocket framed message, then base64_encoded + */ + public static function UnframeMessageProvider() { + return array( + array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7'), + array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg'), + array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow=='), + array( + "The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...", + 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=' + ) + ); + } + + public static function underflowProvider() { + return array( + array('isFinal', ''), + array('getRsv1', ''), + array('getRsv2', ''), + array('getRsv3', ''), + array('getOpcode', ''), + array('isMasked', '10000001'), + array('getPayloadLength', '10000001'), + array('getPayloadLength', '1000000111111110'), + array('getMaskingKey', '1000000110000111'), + array('getPayload', '100000011000000100011100101010101001100111110100') + ); + } + + /** + * @dataProvider underflowProvider + * + * @covers Ratchet\RFC6455\Messaging\Frame::isFinal + * @covers Ratchet\RFC6455\Messaging\Frame::getRsv1 + * @covers Ratchet\RFC6455\Messaging\Frame::getRsv2 + * @covers Ratchet\RFC6455\Messaging\Frame::getRsv3 + * @covers Ratchet\RFC6455\Messaging\Frame::getOpcode + * @covers Ratchet\RFC6455\Messaging\Frame::isMasked + * @covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength + * @covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey + * @covers Ratchet\RFC6455\Messaging\Frame::getPayload + */ + public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) { + $this->setExpectedException('\UnderflowException'); + if (!empty($bin)) { + $this->_frame->addBuffer(static::encode($bin)); + } + call_user_func(array($this->_frame, $method)); + } + + /** + * A data provider for testing the first byte of a WebSocket frame + * param bool Given, is the byte indicate this is the final frame + * param int Given, what is the expected opcode + * param string of 0|1 Each character represents a bit in the byte + */ + public static function firstByteProvider() { + return array( + array(false, false, false, true, 8, '00011000'), + array(true, false, true, false, 10, '10101010'), + array(false, false, false, false, 15, '00001111'), + array(true, false, false, false, 1, '10000001'), + array(true, true, true, true, 15, '11111111'), + array(true, true, false, false, 7, '11000111') + ); + } + + /** + * @dataProvider firstByteProvider + * covers Ratchet\RFC6455\Messaging\Frame::isFinal + */ + public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { + $this->_frame->addBuffer(static::encode($bin)); + $this->assertEquals($fin, $this->_frame->isFinal()); + } + + /** + * @dataProvider firstByteProvider + * covers Ratchet\RFC6455\Messaging\Frame::getRsv1 + * covers Ratchet\RFC6455\Messaging\Frame::getRsv2 + * covers Ratchet\RFC6455\Messaging\Frame::getRsv3 + */ + public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { + $this->_frame->addBuffer(static::encode($bin)); + $this->assertEquals($rsv1, $this->_frame->getRsv1()); + $this->assertEquals($rsv2, $this->_frame->getRsv2()); + $this->assertEquals($rsv3, $this->_frame->getRsv3()); + } + + /** + * @dataProvider firstByteProvider + * covers Ratchet\RFC6455\Messaging\Frame::getOpcode + */ + public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { + $this->_frame->addBuffer(static::encode($bin)); + $this->assertEquals($opcode, $this->_frame->getOpcode()); + } + + /** + * @dataProvider UnframeMessageProvider + * covers Ratchet\RFC6455\Messaging\Frame::isFinal + */ + public function testFinCodeFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertTrue($this->_frame->isFinal()); + } + + /** + * @dataProvider UnframeMessageProvider + * covers Ratchet\RFC6455\Messaging\Frame::getOpcode + */ + public function testOpcodeFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertEquals(1, $this->_frame->getOpcode()); + } + + public static function payloadLengthDescriptionProvider() { + return array( + array(7, '01110101'), + array(7, '01111101'), + array(23, '01111110'), + array(71, '01111111'), + array(7, '00000000'), // Should this throw an exception? Can a payload be empty? + array(7, '00000001') + ); + } + + /** + * @dataProvider payloadLengthDescriptionProvider + * covers Ratchet\RFC6455\Messaging\Frame::addBuffer + * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal + */ + public function testFirstPayloadDesignationValue($bits, $bin) { + $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); + $this->_frame->addBuffer(static::encode($bin)); + $ref = new \ReflectionClass($this->_frame); + $cb = $ref->getMethod('getFirstPayloadVal'); + $cb->setAccessible(true); + $this->assertEquals(bindec($bin), $cb->invoke($this->_frame)); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal + */ + public function testFirstPayloadValUnderflow() { + $ref = new \ReflectionClass($this->_frame); + $cb = $ref->getMethod('getFirstPayloadVal'); + $cb->setAccessible(true); + $this->setExpectedException('UnderflowException'); + $cb->invoke($this->_frame); + } + + /** + * @dataProvider payloadLengthDescriptionProvider + * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits + */ + public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) { + $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); + $this->_frame->addBuffer(static::encode($bin)); + $ref = new \ReflectionClass($this->_frame); + $cb = $ref->getMethod('getNumPayloadBits'); + $cb->setAccessible(true); + $this->assertEquals($expected_bits, $cb->invoke($this->_frame)); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits + */ + public function testgetNumPayloadBitsUnderflow() { + $ref = new \ReflectionClass($this->_frame); + $cb = $ref->getMethod('getNumPayloadBits'); + $cb->setAccessible(true); + $this->setExpectedException('UnderflowException'); + $cb->invoke($this->_frame); + } + + public function secondByteProvider() { + return array( + array(true, 1, '10000001'), + array(false, 1, '00000001'), + array(true, 125, $this->_secondByteMaskedSPL) + ); + } + /** + * @dataProvider secondByteProvider + * covers Ratchet\RFC6455\Messaging\Frame::isMasked + */ + public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) { + $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); + $this->_frame->addBuffer(static::encode($bin)); + $this->assertEquals($masked, $this->_frame->isMasked()); + } + + /** + * @dataProvider UnframeMessageProvider + * covers Ratchet\RFC6455\Messaging\Frame::isMasked + */ + public function testIsMaskedFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertTrue($this->_frame->isMasked()); + } + + /** + * @dataProvider secondByteProvider + * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength + */ + public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) { + $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); + $this->_frame->addBuffer(static::encode($bin)); + $this->assertEquals($payload_length, $this->_frame->getPayloadLength()); + } + + /** + * @dataProvider UnframeMessageProvider + * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength + * @todo Not yet testing when second additional payload length descriptor + */ + public function testGetPayloadLengthFromFullMessage($msg, $encoded) { + $this->_frame->addBuffer(base64_decode($encoded)); + $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength()); + } + + public function maskingKeyProvider() { + $frame = new Frame; + return array( + array($frame->generateMaskingKey()), + array($frame->generateMaskingKey()), + array($frame->generateMaskingKey()) + ); + } + + /** + * @dataProvider maskingKeyProvider + * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey + * @todo I I wrote the dataProvider incorrectly, skipping for now + */ + public function testGetMaskingKey($mask) { + $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); + $this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL)); + $this->_frame->addBuffer($mask); + $this->assertEquals($mask, $this->_frame->getMaskingKey()); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey + */ + public function testGetMaskingKeyOnUnmaskedPayload() { + $frame = new Frame('Hello World!'); + $this->assertEquals('', $frame->getMaskingKey()); + } + + /** + * @dataProvider UnframeMessageProvider + * covers Ratchet\RFC6455\Messaging\Frame::getPayload + * @todo Move this test to bottom as it requires all methods of the class + */ + public function testUnframeFullMessage($unframed, $base_framed) { + $this->_frame->addBuffer(base64_decode($base_framed)); + $this->assertEquals($unframed, $this->_frame->getPayload()); + } + + public static function messageFragmentProvider() { + return array( + array(false, '', '', '', '', '') + ); + } + + /** + * @dataProvider UnframeMessageProvider + * covers Ratchet\RFC6455\Messaging\Frame::getPayload + */ + public function testCheckPiecingTogetherMessage($msg, $encoded) { + $framed = base64_decode($encoded); + for ($i = 0, $len = strlen($framed);$i < $len; $i++) { + $this->_frame->addBuffer(substr($framed, $i, 1)); + } + $this->assertEquals($msg, $this->_frame->getPayload()); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::__construct + * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength + * covers Ratchet\RFC6455\Messaging\Frame::getPayload + */ + public function testLongCreate() { + $len = 65525; + $pl = $this->generateRandomString($len); + $frame = new Frame($pl, true, Frame::OP_PING); + $this->assertTrue($frame->isFinal()); + $this->assertEquals(Frame::OP_PING, $frame->getOpcode()); + $this->assertFalse($frame->isMasked()); + $this->assertEquals($len, $frame->getPayloadLength()); + $this->assertEquals($pl, $frame->getPayload()); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::__construct + * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength + */ + public function testReallyLongCreate() { + $len = 65575; + $frame = new Frame($this->generateRandomString($len)); + $this->assertEquals($len, $frame->getPayloadLength()); + } + /** + * covers Ratchet\RFC6455\Messaging\Frame::__construct + * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow + */ + public function testExtractOverflow() { + $string1 = $this->generateRandomString(); + $frame1 = new Frame($string1); + $string2 = $this->generateRandomString(); + $frame2 = new Frame($string2); + $cat = new Frame; + $cat->addBuffer($frame1->getContents() . $frame2->getContents()); + $this->assertEquals($frame1->getContents(), $cat->getContents()); + $this->assertEquals($string1, $cat->getPayload()); + $uncat = new Frame; + $uncat->addBuffer($cat->extractOverflow()); + $this->assertEquals($string1, $cat->getPayload()); + $this->assertEquals($string2, $uncat->getPayload()); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow + */ + public function testEmptyExtractOverflow() { + $string = $this->generateRandomString(); + $frame = new Frame($string); + $this->assertEquals($string, $frame->getPayload()); + $this->assertEquals('', $frame->extractOverflow()); + $this->assertEquals($string, $frame->getPayload()); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::getContents + */ + public function testGetContents() { + $msg = 'The quick brown fox jumps over the lazy dog.'; + $frame1 = new Frame($msg); + $frame2 = new Frame($msg); + $frame2->maskPayload(); + $this->assertNotEquals($frame1->getContents(), $frame2->getContents()); + $this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents())); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::maskPayload + */ + public function testMasking() { + $msg = 'The quick brown fox jumps over the lazy dog.'; + $frame = new Frame($msg); + $frame->maskPayload(); + $this->assertTrue($frame->isMasked()); + $this->assertEquals($msg, $frame->getPayload()); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::unMaskPayload + */ + public function testUnMaskPayload() { + $string = $this->generateRandomString(); + $frame = new Frame($string); + $frame->maskPayload()->unMaskPayload(); + $this->assertFalse($frame->isMasked()); + $this->assertEquals($string, $frame->getPayload()); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::generateMaskingKey + */ + public function testGenerateMaskingKey() { + $dupe = false; + $done = array(); + for ($i = 0; $i < 10; $i++) { + $new = $this->_frame->generateMaskingKey(); + if (in_array($new, $done)) { + $dupe = true; + } + $done[] = $new; + } + $this->assertEquals(4, strlen($new)); + $this->assertFalse($dupe); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::maskPayload + */ + public function testGivenMaskIsValid() { + $this->setExpectedException('InvalidArgumentException'); + $this->_frame->maskPayload('hello world'); + } + + /** + * covers Ratchet\RFC6455\Messaging\Frame::maskPayload + */ + public function testGivenMaskIsValidAscii() { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped("mbstring required for this test"); + return; + } + $this->setExpectedException('OutOfBoundsException'); + $this->_frame->maskPayload('x✖'); + } + + protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) { + $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง + $useChars = array(); + for($i = 0; $i < $length; $i++) { + $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)]; + } + if($addSpaces === true) { + array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' '); + } + if($addNumbers === true) { + array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9)); + } + shuffle($useChars); + $randomString = trim(implode('', $useChars)); + $randomString = substr($randomString, 0, $length); + return $randomString; + } + + /** + * There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than + * 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame + * to set the payload length to 126 and then not recalculate it once the full length information was available. + * + * This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown. + * + * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength + * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow + */ + public function testFrameDeliveredOneByteAtATime() { + $startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final + $framePayload = str_repeat("*", 256); + $rawOverflow = "xyz"; + $rawFrame = $startHeader . $framePayload . $rawOverflow; + $frame = new Frame(); + $payloadLen = 256; + for ($i = 0; $i < strlen($rawFrame); $i++) { + $frame->addBuffer($rawFrame[$i]); + try { + // payloadLen will + $payloadLen = $frame->getPayloadLength(); + } catch (\UnderflowException $e) { + if ($i > 2) { // we should get an underflow on 0,1,2 + $this->fail("Underflow exception when the frame length should be available"); + } + } + if ($payloadLen !== 256) { + $this->fail("Payload length of " . $payloadLen . " should have been 256."); + } + } + // make sure the overflow is good + $this->assertEquals($rawOverflow, $frame->extractOverflow()); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageBufferTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageBufferTest.php new file mode 100644 index 0000000..c33ff0c --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageBufferTest.php @@ -0,0 +1,39 @@ +getContents(); + + $data = str_repeat($frameRaw, 1000); + + $messageCount = 0; + + $messageBuffer = new MessageBuffer( + new CloseFrameChecker(), + function (Message $message) use (&$messageCount) { + $messageCount++; + $this->assertEquals('a', $message->getPayload()); + }, + null, + false + ); + + $messageBuffer->onData($data); + + $this->assertEquals(1000, $messageCount); + } +} \ No newline at end of file diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageTest.php new file mode 100644 index 0000000..1f7eab5 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageTest.php @@ -0,0 +1,58 @@ +message = new Message; + } + + public function testNoFrames() { + $this->assertFalse($this->message->isCoalesced()); + } + + public function testNoFramesOpCode() { + $this->setExpectedException('UnderflowException'); + $this->message->getOpCode(); + } + + public function testFragmentationPayload() { + $a = 'Hello '; + $b = 'World!'; + $f1 = new Frame($a, false); + $f2 = new Frame($b, true, Frame::OP_CONTINUE); + $this->message->addFrame($f1)->addFrame($f2); + $this->assertEquals(strlen($a . $b), $this->message->getPayloadLength()); + $this->assertEquals($a . $b, $this->message->getPayload()); + } + + public function testUnbufferedFragment() { + $this->message->addFrame(new Frame('The quick brow', false)); + $this->setExpectedException('UnderflowException'); + $this->message->getPayload(); + } + + public function testGetOpCode() { + $this->message + ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) + ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) + ->addFrame(new Frame('er the lazy dog', true, Frame::OP_CONTINUE)) + ; + $this->assertEquals(Frame::OP_TEXT, $this->message->getOpCode()); + } + + public function testGetUnBufferedPayloadLength() { + $this->message + ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) + ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) + ; + $this->assertEquals(28, $this->message->getPayloadLength()); + } +} \ No newline at end of file diff --git a/assets/php/vendor/react/cache/.gitignore b/assets/php/vendor/react/cache/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/assets/php/vendor/react/cache/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/assets/php/vendor/react/cache/.travis.yml b/assets/php/vendor/react/cache/.travis.yml new file mode 100644 index 0000000..290df75 --- /dev/null +++ b/assets/php/vendor/react/cache/.travis.yml @@ -0,0 +1,25 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + +sudo: false + +install: + - composer install --no-interaction + +script: + - ./vendor/bin/phpunit --coverage-text diff --git a/assets/php/vendor/react/cache/CHANGELOG.md b/assets/php/vendor/react/cache/CHANGELOG.md new file mode 100644 index 0000000..19d1801 --- /dev/null +++ b/assets/php/vendor/react/cache/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog + +## 0.4.2 (2017-12-20) + +* Improve documentation with usage and installation instructions + (#10 by @clue) + +* Improve test suite by adding PHPUnit to `require-dev` and + add forward compatibility with PHPUnit 5 and PHPUnit 6 and + sanitize Composer autoload paths + (#14 by @shaunbramley and #12 and #18 by @clue) + +## 0.4.1 (2016-02-25) + +* Repository maintenance, split off from main repo, improve test suite and documentation +* First class support for PHP7 and HHVM (#9 by @clue) +* Adjust compatibility to 5.3 (#7 by @clue) + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to React/Promise 2.0 +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 + +## 0.3.2 (2013-05-10) + +* Version bump + +## 0.3.0 (2013-04-14) + +* Version bump + +## 0.2.6 (2012-12-26) + +* Feature: New cache component, used by DNS diff --git a/assets/php/vendor/react/cache/LICENSE b/assets/php/vendor/react/cache/LICENSE new file mode 100644 index 0000000..a808108 --- /dev/null +++ b/assets/php/vendor/react/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Igor Wiedler, Chris Boden + +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/assets/php/vendor/react/cache/README.md b/assets/php/vendor/react/cache/README.md new file mode 100644 index 0000000..70ad40a --- /dev/null +++ b/assets/php/vendor/react/cache/README.md @@ -0,0 +1,171 @@ +# Cache Component + +[![Build Status](https://secure.travis-ci.org/reactphp/cache.png?branch=master)](http://travis-ci.org/reactphp/cache) [![Code Climate](https://codeclimate.com/github/reactphp/cache/badges/gpa.svg)](https://codeclimate.com/github/reactphp/cache) + +Async, [Promise](https://github.com/reactphp/promise)-based cache interface +for [ReactPHP](https://reactphp.org/). + +The cache component provides a +[Promise](https://github.com/reactphp/promise)-based +[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache) +implementation of that. +This allows consumers to type hint against the interface and third parties to +provide alternate implementations. + +**Table of Contents** + +* [Usage](#usage) + * [CacheInterface](#cacheinterface) + * [get()](#get) + * [set()](#set) + * [remove()](#remove) + * [ArrayCache](#arraycache) +* [Common usage](#common-usage) + * [Fallback get](#fallback-get) + * [Fallback-get-and-set](#fallback-get-and-set) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Usage + +### CacheInterface + +The `CacheInterface` describes the main interface of this component. +This allows consumers to type hint against the interface and third parties to +provide alternate implementations. + +#### get() + +```php +$cache + ->get('foo') + ->then('var_dump'); +``` + +This example fetches the value of the key `foo` and passes it to the +`var_dump` function. You can use any of the composition provided by +[promises](https://github.com/reactphp/promise). + +If the key `foo` does not exist, the promise will be rejected. + +#### set() + +```php +$cache->set('foo', 'bar'); +``` + +This example eventually sets the value of the key `foo` to `bar`. If it +already exists, it is overridden. No guarantees are made as to when the cache +value is set. If the cache implementation has to go over the network to store +it, it may take a while. + +#### remove() + +```php +$cache->remove('foo'); +``` + +This example eventually removes the key `foo` from the cache. As with `set`, +this may not happen instantly. + +### ArrayCache + +The `ArrayCache` provides an in-memory implementation of the +[`CacheInterface`](#cacheinterface). + +```php +$cache = new ArrayCache(); + +$cache->set('foo', 'bar'); +``` + +## Common usage + +### Fallback get + +A common use case of caches is to attempt fetching a cached value and as a +fallback retrieve it from the original data source if not found. Here is an +example of that: + +```php +$cache + ->get('foo') + ->then(null, 'getFooFromDb') + ->then('var_dump'); +``` + +First an attempt is made to retrieve the value of `foo`. A promise rejection +handler of the function `getFooFromDb` is registered. `getFooFromDb` is a +function (can be any PHP callable) that will be called if the key does not +exist in the cache. + +`getFooFromDb` can handle the missing key by returning a promise for the +actual value from the database (or any other data source). As a result, this +chain will correctly fall back, and provide the value in both cases. + +### Fallback get and set + +To expand on the fallback get example, often you want to set the value on the +cache after fetching it from the data source. + +```php +$cache + ->get('foo') + ->then(null, array($this, 'getAndCacheFooFromDb')) + ->then('var_dump'); + +public function getAndCacheFooFromDb() +{ + return $this->db + ->get('foo') + ->then(array($this, 'cacheFooFromDb')); +} + +public function cacheFooFromDb($foo) +{ + $this->cache->set('foo', $foo); + + return $foo; +} +``` + +By using chaining you can easily conditionally cache the value if it is +fetched from the database. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require react/cache:^0.4.2 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and +HHVM. +It's *highly recommended to use PHP 7+* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +## License + +MIT, see [LICENSE file](LICENSE). diff --git a/assets/php/vendor/react/cache/composer.json b/assets/php/vendor/react/cache/composer.json new file mode 100644 index 0000000..51573b6 --- /dev/null +++ b/assets/php/vendor/react/cache/composer.json @@ -0,0 +1,19 @@ +{ + "name": "react/cache", + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": ["cache", "caching", "promise", "ReactPHP"], + "license": "MIT", + "require": { + "php": ">=5.3.0", + "react/promise": "~2.0|~1.1" + }, + "autoload": { + "psr-4": { "React\\Cache\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "React\\Tests\\Cache\\": "tests/" } + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + } +} diff --git a/assets/php/vendor/react/cache/phpunit.xml.dist b/assets/php/vendor/react/cache/phpunit.xml.dist new file mode 100644 index 0000000..d02182f --- /dev/null +++ b/assets/php/vendor/react/cache/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/assets/php/vendor/react/cache/src/ArrayCache.php b/assets/php/vendor/react/cache/src/ArrayCache.php new file mode 100644 index 0000000..03dcc15 --- /dev/null +++ b/assets/php/vendor/react/cache/src/ArrayCache.php @@ -0,0 +1,29 @@ +data[$key])) { + return Promise\reject(); + } + + return Promise\resolve($this->data[$key]); + } + + public function set($key, $value) + { + $this->data[$key] = $value; + } + + public function remove($key) + { + unset($this->data[$key]); + } +} diff --git a/assets/php/vendor/react/cache/src/CacheInterface.php b/assets/php/vendor/react/cache/src/CacheInterface.php new file mode 100644 index 0000000..fd5f2d5 --- /dev/null +++ b/assets/php/vendor/react/cache/src/CacheInterface.php @@ -0,0 +1,13 @@ +cache = new ArrayCache(); + } + + /** @test */ + public function getShouldRejectPromiseForNonExistentKey() + { + $this->cache + ->get('foo') + ->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } + + /** @test */ + public function setShouldSetKey() + { + $this->cache + ->set('foo', 'bar'); + + $success = $this->createCallableMock(); + $success + ->expects($this->once()) + ->method('__invoke') + ->with('bar'); + + $this->cache + ->get('foo') + ->then($success); + } + + /** @test */ + public function removeShouldRemoveKey() + { + $this->cache + ->set('foo', 'bar'); + + $this->cache + ->remove('foo'); + + $this->cache + ->get('foo') + ->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } +} diff --git a/assets/php/vendor/react/cache/tests/CallableStub.php b/assets/php/vendor/react/cache/tests/CallableStub.php new file mode 100644 index 0000000..2f547cd --- /dev/null +++ b/assets/php/vendor/react/cache/tests/CallableStub.php @@ -0,0 +1,10 @@ +createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/.gitignore b/assets/php/vendor/react/dns/.gitignore new file mode 100644 index 0000000..19982ea --- /dev/null +++ b/assets/php/vendor/react/dns/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor \ No newline at end of file diff --git a/assets/php/vendor/react/dns/.travis.yml b/assets/php/vendor/react/dns/.travis.yml new file mode 100644 index 0000000..41921e3 --- /dev/null +++ b/assets/php/vendor/react/dns/.travis.yml @@ -0,0 +1,29 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm # ignore errors, see below + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + allow_failures: + - php: hhvm + +sudo: false + +install: + - composer install --no-interaction + +script: + - vendor/bin/phpunit --coverage-text diff --git a/assets/php/vendor/react/dns/CHANGELOG.md b/assets/php/vendor/react/dns/CHANGELOG.md new file mode 100644 index 0000000..adad0a7 --- /dev/null +++ b/assets/php/vendor/react/dns/CHANGELOG.md @@ -0,0 +1,179 @@ +# Changelog + +## 0.4.13 (2018-02-27) + +* Add `Config::loadSystemConfigBlocking()` to load default system config + and support parsing DNS config on all supported platforms + (`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows) + (#92, #93, #94 and #95 by @clue) + + ```php + $config = Config::loadSystemConfigBlocking(); + $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + ``` + +* Remove unneeded cyclic dependency on react/socket + (#96 by @clue) + +## 0.4.12 (2018-01-14) + +* Improve test suite by adding forward compatibility with PHPUnit 6, + test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases, + add test group to skip integration tests relying on internet connection + and add minor documentation improvements. + (#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor) + +## 0.4.11 (2017-08-25) + +* Feature: Support resolving from default hosts file + (#75, #76 and #77 by @clue) + + This means that resolving hosts such as `localhost` will now work as + expected across all platforms with no changes required: + + ```php + $resolver->resolve('localhost')->then(function ($ip) { + echo 'IP: ' . $ip; + }); + ``` + + The new `HostsExecutor` exists for advanced usage and is otherwise used + internally for this feature. + +## 0.4.10 (2017-08-10) + +* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and + lock minimum dependencies and work around circular dependency for tests + (#70 and #71 by @clue) + +* Fix: Work around DNS timeout issues for Windows users + (#74 by @clue) + +* Documentation and examples for advanced usage + (#66 by @WyriHaximus) + +* Remove broken TCP code, do not retry with invalid TCP query + (#73 by @clue) + +* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and + lock Travis distro so new defaults will not break the build and + fix failing tests for PHP 7.1 + (#68 by @WyriHaximus and #69 and #72 by @clue) + +## 0.4.9 (2017-05-01) + +* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 + (#61 by @clue) + +## 0.4.8 (2017-04-16) + +* Feature: Add support for the AAAA record type to the protocol parser + (#58 by @othillo) + +* Feature: Add support for the PTR record type to the protocol parser + (#59 by @othillo) + +## 0.4.7 (2017-03-31) + +* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component + (#57 by @clue) + +## 0.4.6 (2017-03-11) + +* Fix: Fix DNS timeout issues for Windows users and add forward compatibility + with Stream v0.5 and upcoming v0.6 + (#53 by @clue) + +* Improve test suite by adding PHPUnit to `require-dev` + (#54 by @clue) + +## 0.4.5 (2017-03-02) + +* Fix: Ensure we ignore the case of the answer + (#51 by @WyriHaximus) + +* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal + code re-use for upcoming versions. + (#48 and #49 by @clue) + +## 0.4.4 (2017-02-13) + +* Fix: Fix handling connection and stream errors + (#45 by @clue) + +* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component + (#46 and #47 by @clue) + +## 0.4.3 (2016-07-31) + +* Feature: Allow for cache adapter injection (#38 by @WyriHaximus) + + ```php + $factory = new React\Dns\Resolver\Factory(); + + $cache = new MyCustomCacheInstance(); + $resolver = $factory->createCached('8.8.8.8', $loop, $cache); + ``` + +* Feature: Support Promise cancellation (#35 by @clue) + + ```php + $promise = $resolver->resolve('reactphp.org'); + + $promise->cancel(); + ``` + +## 0.4.2 (2016-02-24) + +* Repository maintenance, split off from main repo, improve test suite and documentation +* First class support for PHP7 and HHVM (#34 by @clue) +* Adjust compatibility to 5.3 (#30 by @clue) + +## 0.4.1 (2014-04-13) + +* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus) + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to React/Promise 2.0 +* Bug fix: Properly resolve CNAME aliases +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 +* Bump React dependencies to v0.4 + +## 0.3.2 (2013-05-10) + +* Feature: Support default port for IPv6 addresses (@clue) + +## 0.3.0 (2013-04-14) + +* Bump React dependencies to v0.3 + +## 0.2.6 (2012-12-26) + +* Feature: New cache component, used by DNS + +## 0.2.5 (2012-11-26) + +* Version bump + +## 0.2.4 (2012-11-18) + +* Feature: Change to promise-based API (@jsor) + +## 0.2.3 (2012-11-14) + +* Version bump + +## 0.2.2 (2012-10-28) + +* Feature: DNS executor timeout handling (@arnaud-lb) +* Feature: DNS retry executor (@arnaud-lb) + +## 0.2.1 (2012-10-14) + +* Minor adjustments to DNS parser + +## 0.2.0 (2012-09-10) + +* Feature: DNS resolver diff --git a/assets/php/vendor/react/dns/LICENSE b/assets/php/vendor/react/dns/LICENSE new file mode 100644 index 0000000..a808108 --- /dev/null +++ b/assets/php/vendor/react/dns/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Igor Wiedler, Chris Boden + +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/assets/php/vendor/react/dns/README.md b/assets/php/vendor/react/dns/README.md new file mode 100644 index 0000000..ed86667 --- /dev/null +++ b/assets/php/vendor/react/dns/README.md @@ -0,0 +1,209 @@ +# Dns + +[![Build Status](https://travis-ci.org/reactphp/dns.svg?branch=master)](https://travis-ci.org/reactphp/dns) + +Async DNS resolver for [ReactPHP](https://reactphp.org/). + +The main point of the DNS component is to provide async DNS resolution. +However, it is really a toolkit for working with DNS messages, and could +easily be used to create a DNS server. + +**Table of contents** + +* [Basic usage](#basic-usage) +* [Caching](#caching) + * [Custom cache adapter](#custom-cache-adapter) +* [Advanced usage](#advanced-usage) + * [HostsFileExecutor](#hostsfileexecutor) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [References](#references) + +## Basic usage + +The most basic usage is to just create a resolver through the resolver +factory. All you need to give it is a nameserver, then you can start resolving +names, baby! + +```php +$loop = React\EventLoop\Factory::create(); + +$config = React\Dns\Config\Config::loadSystemConfigBlocking(); +$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->create($server, $loop); + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); + +$loop->run(); +``` + +See also the [first example](examples). + +The `Config` class can be used to load the system default config. This is an +operation that may access the filesystem and block. Ideally, this method should +thus be executed only once before the loop starts and not repeatedly while it is +running. +Note that this class may return an *empty* configuration if the system config +can not be loaded. As such, you'll likely want to apply a default nameserver +as above if none can be found. + +> Note that the factory loads the hosts file from the filesystem once when + creating the resolver instance. + Ideally, this method should thus be executed only once before the loop starts + and not repeatedly while it is running. + +Pending DNS queries can be cancelled by cancelling its pending promise like so: + +```php +$promise = $resolver->resolve('reactphp.org'); + +$promise->cancel(); +``` + +But there's more. + +## Caching + +You can cache results by configuring the resolver to use a `CachedExecutor`: + +```php +$loop = React\EventLoop\Factory::create(); + +$config = React\Dns\Config\Config::loadSystemConfigBlocking(); +$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->createCached($server, $loop); + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); + +... + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); + +$loop->run(); +``` + +If the first call returns before the second, only one query will be executed. +The second result will be served from an in memory cache. +This is particularly useful for long running scripts where the same hostnames +have to be looked up multiple times. + +See also the [third example](examples). + +### Custom cache adapter + +By default, the above will use an in memory cache. + +You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead: + +```php +$cache = new React\Cache\ArrayCache(); +$loop = React\EventLoop\Factory::create(); +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->createCached('8.8.8.8', $loop, $cache); +``` + +See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations). + +## Advanced Usage + +For more advanced usages one can utilize the `React\Dns\Query\Executor` directly. +The following example looks up the `IPv6` address for `igor.io`. + +```php +$loop = Factory::create(); + +$executor = new Executor($loop, new Parser(), new BinaryDumper(), null); + +$executor->query( + '8.8.8.8:53', + new Query($name, Message::TYPE_AAAA, Message::CLASS_IN, time()) +)->done(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv6: ' . $answer->data . PHP_EOL; + } +}, 'printf'); + +$loop->run(); + +``` + +See also the [fourth example](examples). + +### HostsFileExecutor + +Note that the above `Executor` class always performs an actual DNS query. +If you also want to take entries from your hosts file into account, you may +use this code: + +```php +$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking(); + +$executor = new Executor($loop, new Parser(), new BinaryDumper(), null); +$executor = new HostsFileExecutor($hosts, $executor); + +$executor->query( + '8.8.8.8:53', + new Query('localhost', Message::TYPE_A, Message::CLASS_IN, time()) +); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require react/dns:^0.4.13 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and +HHVM. +It's *highly recommended to use PHP 7+* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +$ php vendor/bin/phpunit --exclude-group internet +``` + +## License + +MIT, see [LICENSE file](LICENSE). + +## References + +* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities +* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification diff --git a/assets/php/vendor/react/dns/composer.json b/assets/php/vendor/react/dns/composer.json new file mode 100644 index 0000000..510a43c --- /dev/null +++ b/assets/php/vendor/react/dns/composer.json @@ -0,0 +1,24 @@ +{ + "name": "react/dns", + "description": "Async DNS resolver for ReactPHP", + "keywords": ["dns", "dns-resolver", "ReactPHP", "async"], + "license": "MIT", + "require": { + "php": ">=5.3.0", + "react/cache": "~0.4.0|~0.3.0", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "^2.1 || ^1.2.1", + "react/promise-timer": "^1.2", + "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "autoload": { + "psr-4": { "React\\Dns\\": "src" } + }, + "autoload-dev": { + "psr-4": { "React\\Tests\\Dns\\": "tests" } + } +} diff --git a/assets/php/vendor/react/dns/examples/01-one.php b/assets/php/vendor/react/dns/examples/01-one.php new file mode 100644 index 0000000..5db164f --- /dev/null +++ b/assets/php/vendor/react/dns/examples/01-one.php @@ -0,0 +1,22 @@ +nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new Factory(); +$resolver = $factory->create($server, $loop); + +$name = isset($argv[1]) ? $argv[1] : 'www.google.com'; + +$resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; +}, 'printf'); + +$loop->run(); diff --git a/assets/php/vendor/react/dns/examples/02-concurrent.php b/assets/php/vendor/react/dns/examples/02-concurrent.php new file mode 100644 index 0000000..87e3f5c --- /dev/null +++ b/assets/php/vendor/react/dns/examples/02-concurrent.php @@ -0,0 +1,27 @@ +nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new Factory(); +$resolver = $factory->create($server, $loop); + +$names = array_slice($argv, 1); +if (!$names) { + $names = array('google.com', 'www.google.com', 'gmail.com'); +} + +foreach ($names as $name) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +} + +$loop->run(); diff --git a/assets/php/vendor/react/dns/examples/03-cached.php b/assets/php/vendor/react/dns/examples/03-cached.php new file mode 100644 index 0000000..e76a27c --- /dev/null +++ b/assets/php/vendor/react/dns/examples/03-cached.php @@ -0,0 +1,40 @@ +nameservers ? reset($config->nameservers) : '8.8.8.8'; + +$factory = new Factory(); +$resolver = $factory->createCached($server, $loop); + +$name = isset($argv[1]) ? $argv[1] : 'www.google.com'; + +$resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; +}, 'printf'); + +$loop->addTimer(1.0, function() use ($name, $resolver) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +}); + +$loop->addTimer(2.0, function() use ($name, $resolver) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +}); + +$loop->addTimer(3.0, function() use ($name, $resolver) { + $resolver->resolve($name)->then(function ($ip) use ($name) { + echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; + }, 'printf'); +}); + +$loop->run(); diff --git a/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php b/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php new file mode 100644 index 0000000..6c46bbf --- /dev/null +++ b/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php @@ -0,0 +1,32 @@ +query('8.8.8.8:53', $ipv4Query)->done(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv4: ' . $answer->data . PHP_EOL; + } +}, 'printf'); +$executor->query('8.8.8.8:53', $ipv6Query)->done(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv6: ' . $answer->data . PHP_EOL; + } +}, 'printf'); + +$loop->run(); diff --git a/assets/php/vendor/react/dns/phpunit.xml.dist b/assets/php/vendor/react/dns/phpunit.xml.dist new file mode 100644 index 0000000..13d3fab --- /dev/null +++ b/assets/php/vendor/react/dns/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/assets/php/vendor/react/dns/src/BadServerException.php b/assets/php/vendor/react/dns/src/BadServerException.php new file mode 100644 index 0000000..3bf50f1 --- /dev/null +++ b/assets/php/vendor/react/dns/src/BadServerException.php @@ -0,0 +1,7 @@ +nameservers = $matches[1]; + + return $config; + } + + /** + * Loads the DNS configurations from Windows's WMIC (from the given command or default command) + * + * Note that this method blocks while loading the given command and should + * thus be used with care! While this should be relatively fast for normal + * WMIC commands, it remains unknown if this may block under certain + * circumstances. In particular, this method should only be executed before + * the loop starts, not while it is running. + * + * Note that this method will only try to execute the given command try to + * parse its output, irrespective of whether this command exists. In + * particular, this command is only available on Windows. Currently, this + * will only parse valid nameserver entries from the command output and will + * ignore all other output without complaining. + * + * Note that the previous section implies that this may return an empty + * `Config` object if no valid nameserver entries can be found. + * + * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing + * @return self + * @link https://ss64.com/nt/wmic.html + */ + public static function loadWmicBlocking($command = null) + { + $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command); + preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches); + + $config = new self(); + $config->nameservers = $matches[1]; + + return $config; + } + + public $nameservers = array(); +} diff --git a/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php new file mode 100644 index 0000000..68cec3e --- /dev/null +++ b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php @@ -0,0 +1,73 @@ +loop = $loop; + } + + public function create($filename) + { + return $this + ->loadEtcResolvConf($filename) + ->then(array($this, 'parseEtcResolvConf')); + } + + /** + * @param string $contents + * @return Promise + * @deprecated see Config instead + */ + public function parseEtcResolvConf($contents) + { + return Promise\resolve(Config::loadResolvConfBlocking( + 'data://text/plain;base64,' . base64_encode($contents) + )); + } + + public function loadEtcResolvConf($filename) + { + if (!file_exists($filename)) { + return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename")); + } + + try { + $deferred = new Deferred(); + + $fd = fopen($filename, 'r'); + stream_set_blocking($fd, 0); + + $contents = ''; + + $stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop); + $stream->on('data', function ($data) use (&$contents) { + $contents .= $data; + }); + $stream->on('end', function () use (&$contents, $deferred) { + $deferred->resolve($contents); + }); + $stream->on('error', function ($error) use ($deferred) { + $deferred->reject($error); + }); + + return $deferred->promise(); + } catch (\Exception $e) { + return Promise\reject($e); + } + } +} diff --git a/assets/php/vendor/react/dns/src/Config/HostsFile.php b/assets/php/vendor/react/dns/src/Config/HostsFile.php new file mode 100644 index 0000000..5b6277e --- /dev/null +++ b/assets/php/vendor/react/dns/src/Config/HostsFile.php @@ -0,0 +1,151 @@ +contents = $contents; + } + + /** + * Returns all IPs for the given hostname + * + * @param string $name + * @return string[] + */ + public function getIpsForHost($name) + { + $name = strtolower($name); + + $ips = array(); + foreach (preg_split('/\r?\n/', $this->contents) as $line) { + $parts = preg_split('/\s+/', $line); + $ip = array_shift($parts); + if ($parts && array_search($name, $parts) !== false) { + // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) + if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) { + $ip = substr($ip, 0, $pos); + } + + if (@inet_pton($ip) !== false) { + $ips[] = $ip; + } + } + } + + return $ips; + } + + /** + * Returns all hostnames for the given IPv4 or IPv6 address + * + * @param string $ip + * @return string[] + */ + public function getHostsForIp($ip) + { + // check binary representation of IP to avoid string case and short notation + $ip = @inet_pton($ip); + if ($ip === false) { + return array(); + } + + $names = array(); + foreach (preg_split('/\r?\n/', $this->contents) as $line) { + $parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY); + $addr = array_shift($parts); + + // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) + if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) { + $addr = substr($addr, 0, $pos); + } + + if (@inet_pton($addr) === $ip) { + foreach ($parts as $part) { + $names[] = $part; + } + } + } + + return $names; + } +} diff --git a/assets/php/vendor/react/dns/src/Model/HeaderBag.php b/assets/php/vendor/react/dns/src/Model/HeaderBag.php new file mode 100644 index 0000000..193e65c --- /dev/null +++ b/assets/php/vendor/react/dns/src/Model/HeaderBag.php @@ -0,0 +1,56 @@ + 0, + 'anCount' => 0, + 'nsCount' => 0, + 'arCount' => 0, + 'qr' => 0, + 'opcode' => Message::OPCODE_QUERY, + 'aa' => 0, + 'tc' => 0, + 'rd' => 0, + 'ra' => 0, + 'z' => 0, + 'rcode' => Message::RCODE_OK, + ); + + public function get($name) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + + public function isQuery() + { + return 0 === $this->attributes['qr']; + } + + public function isResponse() + { + return 1 === $this->attributes['qr']; + } + + public function isTruncated() + { + return 1 === $this->attributes['tc']; + } + + public function populateCounts(Message $message) + { + $this->attributes['qdCount'] = count($message->questions); + $this->attributes['anCount'] = count($message->answers); + $this->attributes['nsCount'] = count($message->authority); + $this->attributes['arCount'] = count($message->additional); + } +} diff --git a/assets/php/vendor/react/dns/src/Model/Message.php b/assets/php/vendor/react/dns/src/Model/Message.php new file mode 100644 index 0000000..715cb1f --- /dev/null +++ b/assets/php/vendor/react/dns/src/Model/Message.php @@ -0,0 +1,100 @@ +header->set('id', self::generateId()); + $request->header->set('rd', 1); + $request->questions[] = (array) $query; + $request->prepare(); + + return $request; + } + + /** + * Creates a new response message for the given query with the given answer records + * + * @param Query $query + * @param Record[] $answers + * @return self + */ + public static function createResponseWithAnswersForQuery(Query $query, array $answers) + { + $response = new Message(); + $response->header->set('id', self::generateId()); + $response->header->set('qr', 1); + $response->header->set('opcode', Message::OPCODE_QUERY); + $response->header->set('rd', 1); + $response->header->set('rcode', Message::RCODE_OK); + + $response->questions[] = (array) $query; + + foreach ($answers as $record) { + $response->answers[] = $record; + } + + $response->prepare(); + + return $response; + } + + private static function generateId() + { + return mt_rand(0, 0xffff); + } + + public $data = ''; + + public $header; + public $questions = array(); + public $answers = array(); + public $authority = array(); + public $additional = array(); + + public $consumed = 0; + + public function __construct() + { + $this->header = new HeaderBag(); + } + + public function prepare() + { + $this->header->populateCounts($this); + } +} diff --git a/assets/php/vendor/react/dns/src/Model/Record.php b/assets/php/vendor/react/dns/src/Model/Record.php new file mode 100644 index 0000000..029d232 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Model/Record.php @@ -0,0 +1,21 @@ +name = $name; + $this->type = $type; + $this->class = $class; + $this->ttl = $ttl; + $this->data = $data; + } +} diff --git a/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php new file mode 100644 index 0000000..35d6ae6 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php @@ -0,0 +1,62 @@ +headerToBinary($message->header); + $data .= $this->questionToBinary($message->questions); + + return $data; + } + + private function headerToBinary(HeaderBag $header) + { + $data = ''; + + $data .= pack('n', $header->get('id')); + + $flags = 0x00; + $flags = ($flags << 1) | $header->get('qr'); + $flags = ($flags << 4) | $header->get('opcode'); + $flags = ($flags << 1) | $header->get('aa'); + $flags = ($flags << 1) | $header->get('tc'); + $flags = ($flags << 1) | $header->get('rd'); + $flags = ($flags << 1) | $header->get('ra'); + $flags = ($flags << 3) | $header->get('z'); + $flags = ($flags << 4) | $header->get('rcode'); + + $data .= pack('n', $flags); + + $data .= pack('n', $header->get('qdCount')); + $data .= pack('n', $header->get('anCount')); + $data .= pack('n', $header->get('nsCount')); + $data .= pack('n', $header->get('arCount')); + + return $data; + } + + private function questionToBinary(array $questions) + { + $data = ''; + + foreach ($questions as $question) { + $labels = explode('.', $question['name']); + foreach ($labels as $label) { + $data .= chr(strlen($label)).$label; + } + $data .= "\x00"; + + $data .= pack('n*', $question['type'], $question['class']); + } + + return $data; + } +} diff --git a/assets/php/vendor/react/dns/src/Protocol/Parser.php b/assets/php/vendor/react/dns/src/Protocol/Parser.php new file mode 100644 index 0000000..1191cd3 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Protocol/Parser.php @@ -0,0 +1,254 @@ +parse($data, $message) !== $message) { + throw new InvalidArgumentException('Unable to parse binary message'); + } + + return $message; + } + + /** + * @deprecated unused, exists for BC only + */ + public function parseChunk($data, Message $message) + { + return $this->parse($data, $message); + } + + private function parse($data, Message $message) + { + $message->data .= $data; + + if (!$message->header->get('id')) { + if (!$this->parseHeader($message)) { + return; + } + } + + if ($message->header->get('qdCount') != count($message->questions)) { + if (!$this->parseQuestion($message)) { + return; + } + } + + if ($message->header->get('anCount') != count($message->answers)) { + if (!$this->parseAnswer($message)) { + return; + } + } + + return $message; + } + + public function parseHeader(Message $message) + { + if (strlen($message->data) < 12) { + return; + } + + $header = substr($message->data, 0, 12); + $message->consumed += 12; + + list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header)); + + $rcode = $fields & bindec('1111'); + $z = ($fields >> 4) & bindec('111'); + $ra = ($fields >> 7) & 1; + $rd = ($fields >> 8) & 1; + $tc = ($fields >> 9) & 1; + $aa = ($fields >> 10) & 1; + $opcode = ($fields >> 11) & bindec('1111'); + $qr = ($fields >> 15) & 1; + + $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount', + 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode'); + + + foreach ($vars as $name => $value) { + $message->header->set($name, $value); + } + + return $message; + } + + public function parseQuestion(Message $message) + { + if (strlen($message->data) < 2) { + return; + } + + $consumed = $message->consumed; + + list($labels, $consumed) = $this->readLabels($message->data, $consumed); + + if (null === $labels) { + return; + } + + if (strlen($message->data) - $consumed < 4) { + return; + } + + list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4))); + $consumed += 4; + + $message->consumed = $consumed; + + $message->questions[] = array( + 'name' => implode('.', $labels), + 'type' => $type, + 'class' => $class, + ); + + if ($message->header->get('qdCount') != count($message->questions)) { + return $this->parseQuestion($message); + } + + return $message; + } + + public function parseAnswer(Message $message) + { + if (strlen($message->data) < 2) { + return; + } + + $consumed = $message->consumed; + + list($labels, $consumed) = $this->readLabels($message->data, $consumed); + + if (null === $labels) { + return; + } + + if (strlen($message->data) - $consumed < 10) { + return; + } + + list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4))); + $consumed += 4; + + list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4))); + $consumed += 4; + + list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2))); + $consumed += 2; + + $rdata = null; + + if (Message::TYPE_A === $type || Message::TYPE_AAAA === $type) { + $ip = substr($message->data, $consumed, $rdLength); + $consumed += $rdLength; + + $rdata = inet_ntop($ip); + } + + if (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type) { + list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed); + + $rdata = implode('.', $bodyLabels); + } + + $message->consumed = $consumed; + + $name = implode('.', $labels); + $ttl = $this->signedLongToUnsignedLong($ttl); + $record = new Record($name, $type, $class, $ttl, $rdata); + + $message->answers[] = $record; + + if ($message->header->get('anCount') != count($message->answers)) { + return $this->parseAnswer($message); + } + + return $message; + } + + private function readLabels($data, $consumed) + { + $labels = array(); + + while (true) { + if ($this->isEndOfLabels($data, $consumed)) { + $consumed += 1; + break; + } + + if ($this->isCompressedLabel($data, $consumed)) { + list($newLabels, $consumed) = $this->getCompressedLabel($data, $consumed); + $labels = array_merge($labels, $newLabels); + break; + } + + $length = ord(substr($data, $consumed, 1)); + $consumed += 1; + + if (strlen($data) - $consumed < $length) { + return array(null, null); + } + + $labels[] = substr($data, $consumed, $length); + $consumed += $length; + } + + return array($labels, $consumed); + } + + public function isEndOfLabels($data, $consumed) + { + $length = ord(substr($data, $consumed, 1)); + return 0 === $length; + } + + public function getCompressedLabel($data, $consumed) + { + list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed); + list($labels) = $this->readLabels($data, $nameOffset); + + return array($labels, $consumed); + } + + public function isCompressedLabel($data, $consumed) + { + $mask = 0xc000; // 1100000000000000 + list($peek) = array_values(unpack('n', substr($data, $consumed, 2))); + + return (bool) ($peek & $mask); + } + + public function getCompressedLabelOffset($data, $consumed) + { + $mask = 0x3fff; // 0011111111111111 + list($peek) = array_values(unpack('n', substr($data, $consumed, 2))); + + return array($peek & $mask, $consumed + 2); + } + + public function signedLongToUnsignedLong($i) + { + return $i & 0x80000000 ? $i - 0xffffffff : $i; + } +} diff --git a/assets/php/vendor/react/dns/src/Query/CachedExecutor.php b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php new file mode 100644 index 0000000..285936d --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php @@ -0,0 +1,55 @@ +executor = $executor; + $this->cache = $cache; + } + + public function query($nameserver, Query $query) + { + $executor = $this->executor; + $cache = $this->cache; + + return $this->cache + ->lookup($query) + ->then( + function ($cachedRecords) use ($query) { + return Message::createResponseWithAnswersForQuery($query, $cachedRecords); + }, + function () use ($executor, $cache, $nameserver, $query) { + return $executor + ->query($nameserver, $query) + ->then(function ($response) use ($cache, $query) { + $cache->storeResponseMessage($query->currentTime, $response); + return $response; + }); + } + ); + } + + /** + * @deprecated unused, exists for BC only + */ + public function buildResponse(Query $query, array $cachedRecords) + { + return Message::createResponseWithAnswersForQuery($query, $cachedRecords); + } + + /** + * @deprecated unused, exists for BC only + */ + protected function generateId() + { + return mt_rand(0, 0xffff); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/CancellationException.php b/assets/php/vendor/react/dns/src/Query/CancellationException.php new file mode 100644 index 0000000..ac30f4c --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/CancellationException.php @@ -0,0 +1,7 @@ +loop = $loop; + $this->parser = $parser; + $this->dumper = $dumper; + $this->timeout = $timeout; + } + + public function query($nameserver, Query $query) + { + $request = Message::createRequestForQuery($query); + + $queryData = $this->dumper->toBinary($request); + $transport = strlen($queryData) > 512 ? 'tcp' : 'udp'; + + return $this->doQuery($nameserver, $transport, $queryData, $query->name); + } + + /** + * @deprecated unused, exists for BC only + */ + public function prepareRequest(Query $query) + { + return Message::createRequestForQuery($query); + } + + public function doQuery($nameserver, $transport, $queryData, $name) + { + // we only support UDP right now + if ($transport !== 'udp') { + return Promise\reject(new \RuntimeException( + 'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version' + )); + } + + $that = $this; + $parser = $this->parser; + $loop = $this->loop; + + // UDP connections are instant, so try this without a timer + try { + $conn = $this->createConnection($nameserver, $transport); + } catch (\Exception $e) { + return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e)); + } + + $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) { + $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name))); + + if ($timer !== null) { + $loop->cancelTimer($timer); + } + $conn->close(); + }); + + $timer = null; + if ($this->timeout !== null) { + $timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) { + $conn->close(); + $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name))); + }); + } + + $conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) { + $conn->end(); + if ($timer !== null) { + $loop->cancelTimer($timer); + } + + try { + $response = $parser->parseMessage($data); + } catch (\Exception $e) { + $deferred->reject($e); + return; + } + + if ($response->header->isTruncated()) { + $deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported')); + return; + } + + $deferred->resolve($response); + }); + $conn->write($queryData); + + return $deferred->promise(); + } + + /** + * @deprecated unused, exists for BC only + */ + protected function generateId() + { + return mt_rand(0, 0xffff); + } + + /** + * @param string $nameserver + * @param string $transport + * @return \React\Stream\DuplexStreamInterface + */ + protected function createConnection($nameserver, $transport) + { + $fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT); + if ($fd === false) { + throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno); + } + + // Instantiate stream instance around this stream resource. + // This ought to be replaced with a datagram socket in the future. + // Temporary work around for Windows 10: buffer whole UDP response + // @coverageIgnoreStart + if (!class_exists('React\Stream\Stream')) { + // prefer DuplexResourceStream as of react/stream v0.7.0 + $conn = new DuplexResourceStream($fd, $this->loop, -1); + } else { + // use legacy Stream class for react/stream < v0.7.0 + $conn = new Stream($fd, $this->loop); + $conn->bufferSize = null; + } + // @coverageIgnoreEnd + + return $conn; + } +} diff --git a/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php new file mode 100644 index 0000000..2f7a635 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php @@ -0,0 +1,8 @@ +hosts = $hosts; + $this->fallback = $fallback; + } + + public function query($nameserver, Query $query) + { + if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) { + // forward lookup for type A or AAAA + $records = array(); + $expectsColon = $query->type === Message::TYPE_AAAA; + foreach ($this->hosts->getIpsForHost($query->name) as $ip) { + // ensure this is an IPv4/IPV6 address according to query type + if ((strpos($ip, ':') !== false) === $expectsColon) { + $records[] = new Record($query->name, $query->type, $query->class, 0, $ip); + } + } + + if ($records) { + return Promise\resolve( + Message::createResponseWithAnswersForQuery($query, $records) + ); + } + } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) { + // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain + $ip = $this->getIpFromHost($query->name); + + if ($ip !== null) { + $records = array(); + foreach ($this->hosts->getHostsForIp($ip) as $host) { + $records[] = new Record($query->name, $query->type, $query->class, 0, $host); + } + + if ($records) { + return Promise\resolve( + Message::createResponseWithAnswersForQuery($query, $records) + ); + } + } + } + + return $this->fallback->query($nameserver, $query); + } + + private function getIpFromHost($host) + { + if (substr($host, -13) === '.in-addr.arpa') { + // IPv4: read as IP and reverse bytes + $ip = @inet_pton(substr($host, 0, -13)); + if ($ip === false || isset($ip[4])) { + return null; + } + + return inet_ntop(strrev($ip)); + } elseif (substr($host, -9) === '.ip6.arpa') { + // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string + $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9))))); + if ($ip === false) { + return null; + } + + return $ip; + } else { + return null; + } + } +} diff --git a/assets/php/vendor/react/dns/src/Query/Query.php b/assets/php/vendor/react/dns/src/Query/Query.php new file mode 100644 index 0000000..aef6e05 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/Query.php @@ -0,0 +1,19 @@ +name = $name; + $this->type = $type; + $this->class = $class; + $this->currentTime = $currentTime; + } +} diff --git a/assets/php/vendor/react/dns/src/Query/RecordBag.php b/assets/php/vendor/react/dns/src/Query/RecordBag.php new file mode 100644 index 0000000..358cf5d --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/RecordBag.php @@ -0,0 +1,27 @@ +records[$record->data] = array($currentTime + $record->ttl, $record); + } + + public function all() + { + return array_values(array_map( + function ($value) { + list($expiresAt, $record) = $value; + return $record; + }, + $this->records + )); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/RecordCache.php b/assets/php/vendor/react/dns/src/Query/RecordCache.php new file mode 100644 index 0000000..b8142d3 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/RecordCache.php @@ -0,0 +1,82 @@ +cache = $cache; + } + + public function lookup(Query $query) + { + $id = $this->serializeQueryToIdentity($query); + + $expiredAt = $this->expiredAt; + + return $this->cache + ->get($id) + ->then(function ($value) use ($query, $expiredAt) { + $recordBag = unserialize($value); + + if (null !== $expiredAt && $expiredAt <= $query->currentTime) { + return Promise\reject(); + } + + return $recordBag->all(); + }); + } + + public function storeResponseMessage($currentTime, Message $message) + { + foreach ($message->answers as $record) { + $this->storeRecord($currentTime, $record); + } + } + + public function storeRecord($currentTime, Record $record) + { + $id = $this->serializeRecordToIdentity($record); + + $cache = $this->cache; + + $this->cache + ->get($id) + ->then( + function ($value) { + return unserialize($value); + }, + function ($e) { + return new RecordBag(); + } + ) + ->then(function ($recordBag) use ($id, $currentTime, $record, $cache) { + $recordBag->set($currentTime, $record); + $cache->set($id, serialize($recordBag)); + }); + } + + public function expire($currentTime) + { + $this->expiredAt = $currentTime; + } + + public function serializeQueryToIdentity(Query $query) + { + return sprintf('%s:%s:%s', $query->name, $query->type, $query->class); + } + + public function serializeRecordToIdentity(Record $record) + { + return sprintf('%s:%s:%s', $record->name, $record->type, $record->class); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/RetryExecutor.php b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php new file mode 100644 index 0000000..90353e5 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php @@ -0,0 +1,44 @@ +executor = $executor; + $this->retries = $retries; + } + + public function query($nameserver, Query $query) + { + return $this->tryQuery($nameserver, $query, $this->retries); + } + + public function tryQuery($nameserver, Query $query, $retries) + { + $that = $this; + $errorback = function ($error) use ($nameserver, $query, $retries, $that) { + if (!$error instanceof TimeoutException) { + throw $error; + } + if (0 >= $retries) { + throw new \RuntimeException( + sprintf("DNS query for %s failed: too many retries", $query->name), + 0, + $error + ); + } + return $that->tryQuery($nameserver, $query, $retries-1); + }; + + return $this->executor + ->query($nameserver, $query) + ->then(null, $errorback); + } +} diff --git a/assets/php/vendor/react/dns/src/Query/TimeoutException.php b/assets/php/vendor/react/dns/src/Query/TimeoutException.php new file mode 100644 index 0000000..90bf806 --- /dev/null +++ b/assets/php/vendor/react/dns/src/Query/TimeoutException.php @@ -0,0 +1,7 @@ +executor = $executor; + $this->loop = $loop; + $this->timeout = $timeout; + } + + public function query($nameserver, Query $query) + { + return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) { + if ($e instanceof Timer\TimeoutException) { + $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e); + } + throw $e; + }); + } +} diff --git a/assets/php/vendor/react/dns/src/RecordNotFoundException.php b/assets/php/vendor/react/dns/src/RecordNotFoundException.php new file mode 100644 index 0000000..0028413 --- /dev/null +++ b/assets/php/vendor/react/dns/src/RecordNotFoundException.php @@ -0,0 +1,7 @@ +addPortToServerIfMissing($nameserver); + $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop)); + + return new Resolver($nameserver, $executor); + } + + public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null) + { + if (!($cache instanceof CacheInterface)) { + $cache = new ArrayCache(); + } + + $nameserver = $this->addPortToServerIfMissing($nameserver); + $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache)); + + return new Resolver($nameserver, $executor); + } + + /** + * Tries to load the hosts file and decorates the given executor on success + * + * @param ExecutorInterface $executor + * @return ExecutorInterface + * @codeCoverageIgnore + */ + private function decorateHostsFileExecutor(ExecutorInterface $executor) + { + try { + $executor = new HostsFileExecutor( + HostsFile::loadFromPathBlocking(), + $executor + ); + } catch (\RuntimeException $e) { + // ignore this file if it can not be loaded + } + + // Windows does not store localhost in hosts file by default but handles this internally + // To compensate for this, we explicitly use hard-coded defaults for localhost + if (DIRECTORY_SEPARATOR === '\\') { + $executor = new HostsFileExecutor( + new HostsFile("127.0.0.1 localhost\n::1 localhost"), + $executor + ); + } + + return $executor; + } + + protected function createExecutor(LoopInterface $loop) + { + return new TimeoutExecutor( + new Executor($loop, new Parser(), new BinaryDumper(), null), + 5.0, + $loop + ); + } + + protected function createRetryExecutor(LoopInterface $loop) + { + return new RetryExecutor($this->createExecutor($loop)); + } + + protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache) + { + return new CachedExecutor($this->createRetryExecutor($loop), new RecordCache($cache)); + } + + protected function addPortToServerIfMissing($nameserver) + { + if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) { + // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets + $nameserver = '[' . $nameserver . ']'; + } + // assume a dummy scheme when checking for the port, otherwise parse_url() fails + if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) { + $nameserver .= ':53'; + } + + return $nameserver; + } +} diff --git a/assets/php/vendor/react/dns/src/Resolver/Resolver.php b/assets/php/vendor/react/dns/src/Resolver/Resolver.php new file mode 100644 index 0000000..4a4983a --- /dev/null +++ b/assets/php/vendor/react/dns/src/Resolver/Resolver.php @@ -0,0 +1,100 @@ +nameserver = $nameserver; + $this->executor = $executor; + } + + public function resolve($domain) + { + $query = new Query($domain, Message::TYPE_A, Message::CLASS_IN, time()); + $that = $this; + + return $this->executor + ->query($this->nameserver, $query) + ->then(function (Message $response) use ($query, $that) { + return $that->extractAddress($query, $response); + }); + } + + public function extractAddress(Query $query, Message $response) + { + $answers = $response->answers; + + $addresses = $this->resolveAliases($answers, $query->name); + + if (0 === count($addresses)) { + $message = 'DNS Request did not return valid answer.'; + throw new RecordNotFoundException($message); + } + + $address = $addresses[array_rand($addresses)]; + return $address; + } + + public function resolveAliases(array $answers, $name) + { + $named = $this->filterByName($answers, $name); + $aRecords = $this->filterByType($named, Message::TYPE_A); + $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME); + + if ($aRecords) { + return $this->mapRecordData($aRecords); + } + + if ($cnameRecords) { + $aRecords = array(); + + $cnames = $this->mapRecordData($cnameRecords); + foreach ($cnames as $cname) { + $targets = $this->filterByName($answers, $cname); + $aRecords = array_merge( + $aRecords, + $this->resolveAliases($answers, $cname) + ); + } + + return $aRecords; + } + + return array(); + } + + private function filterByName(array $answers, $name) + { + return $this->filterByField($answers, 'name', $name); + } + + private function filterByType(array $answers, $type) + { + return $this->filterByField($answers, 'type', $type); + } + + private function filterByField(array $answers, $field, $value) + { + $value = strtolower($value); + return array_filter($answers, function ($answer) use ($field, $value) { + return $value === strtolower($answer->$field); + }); + } + + private function mapRecordData(array $records) + { + return array_map(function ($record) { + return $record->data; + }, $records); + } +} diff --git a/assets/php/vendor/react/dns/tests/CallableStub.php b/assets/php/vendor/react/dns/tests/CallableStub.php new file mode 100644 index 0000000..a34a263 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/CallableStub.php @@ -0,0 +1,10 @@ +assertInstanceOf('React\Dns\Config\Config', $config); + } + + public function testLoadsDefaultPath() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Not supported on Windows'); + } + + $config = Config::loadResolvConfBlocking(); + + $this->assertInstanceOf('React\Dns\Config\Config', $config); + } + + public function testLoadsFromExplicitPath() + { + $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf'); + + $this->assertEquals(array('8.8.8.8'), $config->nameservers); + } + + /** + * @expectedException RuntimeException + */ + public function testLoadThrowsWhenPathIsInvalid() + { + Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf'); + } + + public function testParsesSingleEntryFile() + { + $contents = 'nameserver 8.8.8.8'; + $expected = array('8.8.8.8'); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents)); + $this->assertEquals($expected, $config->nameservers); + } + + public function testParsesNameserverEntriesFromAverageFileCorrectly() + { + $contents = '# +# Mac OS X Notice +# +# This file is not used by the host name and address resolution +# or the DNS query routing mechanisms used by most processes on +# this Mac OS X system. +# +# This file is automatically generated. +# +domain v.cablecom.net +nameserver 127.0.0.1 +nameserver ::1 +'; + $expected = array('127.0.0.1', '::1'); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents)); + $this->assertEquals($expected, $config->nameservers); + } + + public function testParsesEmptyFileWithoutNameserverEntries() + { + $contents = ''; + $expected = array(); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,'); + $this->assertEquals($expected, $config->nameservers); + } + + public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries() + { + $contents = ' +# nameserver 1.2.3.4 +; nameserver 2.3.4.5 + +nameserver 3.4.5.6 # nope +nameserver 4.5.6.7 5.6.7.8 + nameserver 6.7.8.9 +NameServer 7.8.9.10 +'; + $expected = array(); + + $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents)); + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsFromWmicOnWindows() + { + if (DIRECTORY_SEPARATOR !== '\\') { + $this->markTestSkipped('Only on Windows'); + } + + $config = Config::loadWmicBlocking(); + + $this->assertInstanceOf('React\Dns\Config\Config', $config); + } + + public function testLoadsSingleEntryFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1} +ACE, +'; + $expected = array('192.168.2.1'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsEmptyListFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +'; + $expected = array(); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsSingleEntryForMultipleNicsFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1} +ACE, +ACE,{192.168.2.2} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1;192.168.2.2} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{"192.168.2.1","192.168.2.2"} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + private function echoCommand($output) + { + return 'echo ' . escapeshellarg($output); + } +} diff --git a/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php b/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php new file mode 100644 index 0000000..bb9eac7 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php @@ -0,0 +1,70 @@ +getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $factory = new FilesystemFactory($loop); + $factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) { + $capturedConfig = $config; + }); + + $this->assertNotNull($capturedConfig); + $this->assertSame($expected, $capturedConfig->nameservers); + } + + /** @test */ + public function createShouldLoadStuffFromFilesystem() + { + $this->markTestIncomplete('Filesystem API is incomplete'); + + $expected = array('8.8.8.8'); + + $triggerListener = null; + $capturedConfig = null; + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop + ->expects($this->once()) + ->method('addReadStream') + ->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) { + $triggerListener = function () use ($stream, $listener) { + call_user_func($listener, $stream); + }; + })); + + $factory = new FilesystemFactory($loop); + $factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) { + $capturedConfig = $config; + }); + + $triggerListener(); + + $this->assertNotNull($capturedConfig); + $this->assertSame($expected, $capturedConfig->nameservers); + } +} diff --git a/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php b/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php new file mode 100644 index 0000000..ff74ad2 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php @@ -0,0 +1,170 @@ +assertInstanceOf('React\Dns\Config\HostsFile', $hosts); + } + + public function testDefaultShouldHaveLocalhostMapped() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Not supported on Windows'); + } + + $hosts = HostsFile::loadFromPathBlocking(); + + $this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost')); + } + + /** + * @expectedException RuntimeException + */ + public function testLoadThrowsForInvalidPath() + { + HostsFile::loadFromPathBlocking('does/not/exist'); + } + + public function testContainsSingleLocalhostEntry() + { + $hosts = new HostsFile('127.0.0.1 localhost'); + + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost')); + $this->assertEquals(array(), $hosts->getIpsForHost('example.com')); + } + + public function testNonIpReturnsNothingForInvalidHosts() + { + $hosts = new HostsFile('a b'); + + $this->assertEquals(array(), $hosts->getIpsForHost('a')); + $this->assertEquals(array(), $hosts->getIpsForHost('b')); + } + + public function testIgnoresIpv6ZoneId() + { + $hosts = new HostsFile('fe80::1%lo0 localhost'); + + $this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost')); + } + + public function testSkipsComments() + { + $hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com'); + + $this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost')); + $this->assertEquals(array(), $hosts->getIpsForHost('example.com')); + } + + public function testContainsSingleLocalhostEntryWithCaseIgnored() + { + $hosts = new HostsFile('127.0.0.1 LocalHost'); + + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST')); + } + + public function testEmptyFileContainsNothing() + { + $hosts = new HostsFile(''); + + $this->assertEquals(array(), $hosts->getIpsForHost('example.com')); + } + + public function testSingleEntryWithMultipleNames() + { + $hosts = new HostsFile('127.0.0.1 localhost example.com'); + + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com')); + $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost')); + } + + public function testMergesEntriesOverMultipleLines() + { + $hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost"); + + $this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost')); + } + + public function testMergesIpv4AndIpv6EntriesOverMultipleLines() + { + $hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost"); + + $this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost')); + } + + public function testReverseLookup() + { + $hosts = new HostsFile('127.0.0.1 localhost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1')); + $this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1')); + } + + public function testReverseSkipsComments() + { + $hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t"); + + $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1')); + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2')); + $this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3')); + } + + public function testReverseNonIpReturnsNothing() + { + $hosts = new HostsFile('127.0.0.1 localhost'); + + $this->assertEquals(array(), $hosts->getHostsForIp('localhost')); + $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1')); + } + + public function testReverseNonIpReturnsNothingForInvalidHosts() + { + $hosts = new HostsFile('a b'); + + $this->assertEquals(array(), $hosts->getHostsForIp('a')); + $this->assertEquals(array(), $hosts->getHostsForIp('b')); + } + + public function testReverseLookupReturnsLowerCaseHost() + { + $hosts = new HostsFile('127.0.0.1 LocalHost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1')); + } + + public function testReverseLookupChecksNormalizedIpv6() + { + $hosts = new HostsFile('FE80::00a1 localhost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1')); + } + + public function testReverseLookupIgnoresIpv6ZoneId() + { + $hosts = new HostsFile('fe80::1%lo0 localhost'); + + $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1')); + } + + public function testReverseLookupReturnsMultipleHostsOverSingleLine() + { + $hosts = new HostsFile("::1 ip6-localhost ip6-loopback"); + + $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1')); + } + + public function testReverseLookupReturnsMultipleHostsOverMultipleLines() + { + $hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback"); + + $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1')); + } +} diff --git a/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf b/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf new file mode 100644 index 0000000..cae093a --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf @@ -0,0 +1 @@ +nameserver 8.8.8.8 diff --git a/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php b/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php new file mode 100644 index 0000000..0807e86 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php @@ -0,0 +1,71 @@ +loop = LoopFactory::create(); + + $factory = new Factory(); + $this->resolver = $factory->create('8.8.8.8', $this->loop); + } + + public function testResolveLocalhostResolves() + { + $promise = $this->resolver->resolve('localhost'); + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $this->loop->run(); + } + + /** + * @group internet + */ + public function testResolveGoogleResolves() + { + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $this->loop->run(); + } + + /** + * @group internet + */ + public function testResolveInvalidRejects() + { + $promise = $this->resolver->resolve('example.invalid'); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + + $this->loop->run(); + } + + public function testResolveCancelledRejectsImmediately() + { + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + $promise->cancel(); + + $time = microtime(true); + $this->loop->run(); + $time = microtime(true) - $time; + + $this->assertLessThan(0.1, $time); + } + + public function testInvalidResolverDoesNotResolveGoogle() + { + $factory = new Factory(); + $this->resolver = $factory->create('255.255.255.255', $this->loop); + + $promise = $this->resolver->resolve('google.com'); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } +} diff --git a/assets/php/vendor/react/dns/tests/Model/MessageTest.php b/assets/php/vendor/react/dns/tests/Model/MessageTest.php new file mode 100644 index 0000000..53d6b28 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Model/MessageTest.php @@ -0,0 +1,30 @@ +assertTrue($request->header->isQuery()); + $this->assertSame(1, $request->header->get('rd')); + } + + public function testCreateResponseWithNoAnswers() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $answers = array(); + $request = Message::createResponseWithAnswersForQuery($query, $answers); + + $this->assertFalse($request->header->isQuery()); + $this->assertTrue($request->header->isResponse()); + $this->assertEquals(0, $request->header->get('anCount')); + } +} diff --git a/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php b/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php new file mode 100644 index 0000000..bf60ca9 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php @@ -0,0 +1,48 @@ +formatHexDump(str_replace(' ', '', $data), 2); + + $request = new Message(); + $request->header->set('id', 0x7262); + $request->header->set('rd', 1); + + $request->questions[] = array( + 'name' => 'igor.io', + 'type' => Message::TYPE_A, + 'class' => Message::CLASS_IN, + ); + + $request->prepare(); + + $dumper = new BinaryDumper(); + $data = $dumper->toBinary($request); + $data = $this->convertBinaryToHexDump($data); + + $this->assertSame($expected, $data); + } + + private function convertBinaryToHexDump($input) + { + return $this->formatHexDump(implode('', unpack('H*', $input))); + } + + private function formatHexDump($input) + { + return implode(' ', str_split($input, 2)); + } +} diff --git a/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php b/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php new file mode 100644 index 0000000..195fad2 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php @@ -0,0 +1,343 @@ +parser = new Parser(); + } + + /** + * @dataProvider provideConvertTcpDumpToBinary + */ + public function testConvertTcpDumpToBinary($expected, $data) + { + $this->assertSame($expected, $this->convertTcpDumpToBinary($data)); + } + + public function provideConvertTcpDumpToBinary() + { + return array( + array(chr(0x72).chr(0x62), "72 62"), + array(chr(0x72).chr(0x62).chr(0x01).chr(0x00), "72 62 01 00"), + array(chr(0x72).chr(0x62).chr(0x01).chr(0x00).chr(0x00).chr(0x01), "72 62 01 00 00 01"), + array(chr(0x01).chr(0x00).chr(0x01), "01 00 01"), + ); + } + + public function testParseRequest() + { + $data = ""; + $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + $data .= "00 01 00 01"; // question: type A, class IN + + $data = $this->convertTcpDumpToBinary($data); + + $request = $this->parser->parseMessage($data); + + $header = $request->header; + $this->assertSame(0x7262, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(0, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(0, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(0, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $request->questions); + $this->assertSame('igor.io', $request->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $request->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']); + } + + public function testParseResponse() + { + $data = ""; + $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + $data .= "00 01 00 01"; // question: type A, class IN + $data .= "c0 0c"; // answer: offset pointer to igor.io + $data .= "00 01 00 01"; // answer: type A, class IN + $data .= "00 01 51 80"; // answer: ttl 86400 + $data .= "00 04"; // answer: rdlength 4 + $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131 + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $header = $response->header; + $this->assertSame(0x7262, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(1, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(1, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(1, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $response->questions); + $this->assertSame('igor.io', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('igor.io', $response->answers[0]->name); + $this->assertSame(Message::TYPE_A, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(86400, $response->answers[0]->ttl); + $this->assertSame('178.79.169.131', $response->answers[0]->data); + } + + public function testParseQuestionWithTwoQuestions() + { + $data = ""; + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + $data .= "00 01 00 01"; // question: type A, class IN + $data .= "03 77 77 77 04 69 67 6f 72 02 69 6f 00"; // question: www.igor.io + $data .= "00 01 00 01"; // question: type A, class IN + + $data = $this->convertTcpDumpToBinary($data); + + $request = new Message(); + $request->header->set('qdCount', 2); + $request->data = $data; + + $this->parser->parseQuestion($request); + + $this->assertCount(2, $request->questions); + $this->assertSame('igor.io', $request->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $request->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']); + $this->assertSame('www.igor.io', $request->questions[1]['name']); + $this->assertSame(Message::TYPE_A, $request->questions[1]['type']); + $this->assertSame(Message::CLASS_IN, $request->questions[1]['class']); + } + + public function testParseAnswerWithInlineData() + { + $data = ""; + $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io + $data .= "00 01 00 01"; // answer: type A, class IN + $data .= "00 01 51 80"; // answer: ttl 86400 + $data .= "00 04"; // answer: rdlength 4 + $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131 + + $data = $this->convertTcpDumpToBinary($data); + + $response = new Message(); + $response->header->set('anCount', 1); + $response->data = $data; + + $this->parser->parseAnswer($response); + + $this->assertCount(1, $response->answers); + $this->assertSame('igor.io', $response->answers[0]->name); + $this->assertSame(Message::TYPE_A, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(86400, $response->answers[0]->ttl); + $this->assertSame('178.79.169.131', $response->answers[0]->data); + } + + public function testParseResponseWithCnameAndOffsetPointers() + { + $data = ""; + $data .= "9e 8d 81 80 00 01 00 01 00 00 00 00"; // header + $data .= "04 6d 61 69 6c 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: mail.google.com + $data .= "00 05 00 01"; // question: type CNAME, class IN + $data .= "c0 0c"; // answer: offset pointer to mail.google.com + $data .= "00 05 00 01"; // answer: type CNAME, class IN + $data .= "00 00 a8 9c"; // answer: ttl 43164 + $data .= "00 0f"; // answer: rdlength 15 + $data .= "0a 67 6f 6f 67 6c 65 6d 61 69 6c 01 6c"; // answer: rdata googlemail.l. + $data .= "c0 11"; // answer: rdata offset pointer to google.com + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $this->assertCount(1, $response->questions); + $this->assertSame('mail.google.com', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_CNAME, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('mail.google.com', $response->answers[0]->name); + $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(43164, $response->answers[0]->ttl); + $this->assertSame('googlemail.l.google.com', $response->answers[0]->data); + } + + public function testParseAAAAResponse() + { + $data = ""; + $data .= "cd 72 81 80 00 01 00 01 00 00 00 00 06"; // header + $data .= "67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: google.com + $data .= "00 1c 00 01"; // question: type AAAA, class IN + $data .= "c0 0c"; // answer: offset pointer to google.com + $data .= "00 1c 00 01"; // answer: type AAAA, class IN + $data .= "00 00 01 2b"; // answer: ttl 299 + $data .= "00 10"; // answer: rdlength 16 + $data .= "2a 00 14 50 40 09 08 09 00 00 00 00 00 00 20 0e"; // answer: 2a00:1450:4009:809::200e + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $header = $response->header; + $this->assertSame(0xcd72, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(1, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(1, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(1, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $response->questions); + $this->assertSame('google.com', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_AAAA, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('google.com', $response->answers[0]->name); + $this->assertSame(Message::TYPE_AAAA, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(299, $response->answers[0]->ttl); + $this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data); + } + + public function testParseResponseWithTwoAnswers() + { + $data = ""; + $data .= "bc 73 81 80 00 01 00 02 00 00 00 00"; // header + $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00"; + // question: io.whois-servers.net + $data .= "00 01 00 01"; // question: type A, class IN + $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net + $data .= "00 05 00 01"; // answer: type CNAME, class IN + $data .= "00 00 00 29"; // answer: ttl 41 + $data .= "00 0e"; // answer: rdlength 14 + $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io + $data .= "c0 32"; // answer: offset pointer to whois.nic.io + $data .= "00 01 00 01"; // answer: type CNAME, class IN + $data .= "00 00 0d f7"; // answer: ttl 3575 + $data .= "00 04"; // answer: rdlength 4 + $data .= "c1 df 4e 98"; // answer: rdata 193.223.78.152 + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $this->assertCount(1, $response->questions); + $this->assertSame('io.whois-servers.net', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_A, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(2, $response->answers); + + $this->assertSame('io.whois-servers.net', $response->answers[0]->name); + $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(41, $response->answers[0]->ttl); + $this->assertSame('whois.nic.io', $response->answers[0]->data); + + $this->assertSame('whois.nic.io', $response->answers[1]->name); + $this->assertSame(Message::TYPE_A, $response->answers[1]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[1]->class); + $this->assertSame(3575, $response->answers[1]->ttl); + $this->assertSame('193.223.78.152', $response->answers[1]->data); + } + + public function testParsePTRResponse() + { + $data = ""; + $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header + $data .= "01 34 01 34 01 38 01 38 07 69 6e"; // question: 4.4.8.8.in-addr.arpa + $data .= "2d 61 64 64 72 04 61 72 70 61 00"; // question (continued) + $data .= "00 0c 00 01"; // question: type PTR, class IN + $data .= "c0 0c"; // answer: offset pointer to rdata + $data .= "00 0c 00 01"; // answer: type PTR, class IN + $data .= "00 01 51 7f"; // answer: ttl 86399 + $data .= "00 20"; // answer: rdlength 32 + $data .= "13 67 6f 6f 67 6c 65 2d 70 75 62 6c 69 63 2d 64"; // answer: rdata google-public-dns-b.google.com. + $data .= "6e 73 2d 62 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; + + $data = $this->convertTcpDumpToBinary($data); + + $response = $this->parser->parseMessage($data); + + $header = $response->header; + $this->assertSame(0x5dd8, $header->get('id')); + $this->assertSame(1, $header->get('qdCount')); + $this->assertSame(1, $header->get('anCount')); + $this->assertSame(0, $header->get('nsCount')); + $this->assertSame(0, $header->get('arCount')); + $this->assertSame(1, $header->get('qr')); + $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode')); + $this->assertSame(0, $header->get('aa')); + $this->assertSame(0, $header->get('tc')); + $this->assertSame(1, $header->get('rd')); + $this->assertSame(1, $header->get('ra')); + $this->assertSame(0, $header->get('z')); + $this->assertSame(Message::RCODE_OK, $header->get('rcode')); + + $this->assertCount(1, $response->questions); + $this->assertSame('4.4.8.8.in-addr.arpa', $response->questions[0]['name']); + $this->assertSame(Message::TYPE_PTR, $response->questions[0]['type']); + $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']); + + $this->assertCount(1, $response->answers); + $this->assertSame('4.4.8.8.in-addr.arpa', $response->answers[0]->name); + $this->assertSame(Message::TYPE_PTR, $response->answers[0]->type); + $this->assertSame(Message::CLASS_IN, $response->answers[0]->class); + $this->assertSame(86399, $response->answers[0]->ttl); + $this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testParseIncomplete() + { + $data = ""; + $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header + $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io + //$data .= "00 01 00 01"; // question: type A, class IN + + $data = $this->convertTcpDumpToBinary($data); + + $this->parser->parseMessage($data); + } + + private function convertTcpDumpToBinary($input) + { + // sudo ngrep -d en1 -x port 53 + + return pack('H*', str_replace(' ', '', $input)); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php new file mode 100644 index 0000000..d08ed05 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php @@ -0,0 +1,100 @@ +createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue($this->createPromiseMock())); + + $cache = $this->getMockBuilder('React\Dns\Query\RecordCache') + ->disableOriginalConstructor() + ->getMock(); + $cache + ->expects($this->once()) + ->method('lookup') + ->will($this->returnValue(Promise\reject())); + $cachedExecutor = new CachedExecutor($executor, $cache); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $cachedExecutor->query('8.8.8.8', $query); + } + + /** + * @covers React\Dns\Query\CachedExecutor + * @test + */ + public function callingQueryTwiceShouldUseCachedResult() + { + $cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN)); + + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->will($this->callQueryCallbackWithAddress('178.79.169.131')); + + $cache = $this->getMockBuilder('React\Dns\Query\RecordCache') + ->disableOriginalConstructor() + ->getMock(); + $cache + ->expects($this->at(0)) + ->method('lookup') + ->with($this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue(Promise\reject())); + $cache + ->expects($this->at(1)) + ->method('storeResponseMessage') + ->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message')); + $cache + ->expects($this->at(2)) + ->method('lookup') + ->with($this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue(Promise\resolve($cachedRecords))); + + $cachedExecutor = new CachedExecutor($executor, $cache); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {}); + $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {}); + } + + private function callQueryCallbackWithAddress($address) + { + return $this->returnCallback(function ($nameserver, $query) use ($address) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address); + + return Promise\resolve($response); + }); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } + + private function createPromiseMock() + { + return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php new file mode 100644 index 0000000..0d7ac1d --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php @@ -0,0 +1,308 @@ +loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock(); + $this->dumper = new BinaryDumper(); + + $this->executor = new Executor($this->loop, $this->parser, $this->dumper); + } + + /** @test */ + public function queryShouldCreateUdpRequest() + { + $timer = $this->createTimerMock(); + $this->loop + ->expects($this->any()) + ->method('addTimer') + ->will($this->returnValue($timer)); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock(false)); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query); + } + + /** @test */ + public function resolveShouldRejectIfRequestIsLargerThan512Bytes() + { + $query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldCloseConnectionWhenCancelled() + { + $conn = $this->createConnectionMock(false); + $conn->expects($this->once())->method('close'); + + $timer = $this->createTimerMock(); + $this->loop + ->expects($this->any()) + ->method('addTimer') + ->will($this->returnValue($timer)); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnValue($conn)); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $promise->cancel(); + + $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull() + { + $this->loop + ->expects($this->never()) + ->method('addTimer'); + + $this->executor = new Executor($this->loop, $this->parser, $this->dumper, null); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('127.0.0.1:53', $query); + + $promise->cancel(); + + $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldRejectIfResponseIsTruncated() + { + $timer = $this->createTimerMock(); + + $this->loop + ->expects($this->any()) + ->method('addTimer') + ->will($this->returnValue($timer)); + + $this->parser + ->expects($this->once()) + ->method('parseMessage') + ->will($this->returnTruncatedResponse()); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock()); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query); + } + + /** @test */ + public function resolveShouldFailIfUdpThrow() + { + $this->loop + ->expects($this->never()) + ->method('addTimer'); + + $this->parser + ->expects($this->never()) + ->method('parseMessage'); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->once()) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->throwException(new \Exception('Nope'))); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope'); + Block\await($promise, $this->loop); + } + + /** @test */ + public function resolveShouldCancelTimerWhenFullResponseIsReceived() + { + $conn = $this->createConnectionMock(); + + $this->parser + ->expects($this->once()) + ->method('parseMessage') + ->will($this->returnStandardResponse()); + + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->at(0)) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock()); + + + $timer = $this->createTimerMock(); + + $this->loop + ->expects($this->once()) + ->method('addTimer') + ->with(5, $this->isInstanceOf('Closure')) + ->will($this->returnValue($timer)); + + $this->loop + ->expects($this->once()) + ->method('cancelTimer') + ->with($timer); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query); + } + + /** @test */ + public function resolveShouldCloseConnectionOnTimeout() + { + $this->executor = $this->createExecutorMock(); + $this->executor + ->expects($this->at(0)) + ->method('createConnection') + ->with('8.8.8.8:53', 'udp') + ->will($this->returnNewConnectionMock(false)); + + $timer = $this->createTimerMock(); + + $this->loop + ->expects($this->never()) + ->method('cancelTimer'); + + $this->loop + ->expects($this->once()) + ->method('addTimer') + ->with(5, $this->isInstanceOf('Closure')) + ->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) { + $timerCallback = $callback; + return $timer; + })); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->assertNotNull($timerCallback); + $timerCallback(); + + $this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out'); + Block\await($promise, $this->loop); + } + + private function returnStandardResponse() + { + $that = $this; + $callback = function ($data) use ($that) { + $response = new Message(); + $that->convertMessageToStandardResponse($response); + return $response; + }; + + return $this->returnCallback($callback); + } + + private function returnTruncatedResponse() + { + $that = $this; + $callback = function ($data) use ($that) { + $response = new Message(); + $that->convertMessageToTruncatedResponse($response); + return $response; + }; + + return $this->returnCallback($callback); + } + + public function convertMessageToStandardResponse(Message $response) + { + $response->header->set('qr', 1); + $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'); + $response->prepare(); + + return $response; + } + + public function convertMessageToTruncatedResponse(Message $response) + { + $this->convertMessageToStandardResponse($response); + $response->header->set('tc', 1); + $response->prepare(); + + return $response; + } + + private function returnNewConnectionMock($emitData = true) + { + $conn = $this->createConnectionMock($emitData); + + $callback = function () use ($conn) { + return $conn; + }; + + return $this->returnCallback($callback); + } + + private function createConnectionMock($emitData = true) + { + $conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock(); + $conn + ->expects($this->any()) + ->method('on') + ->with('data', $this->isInstanceOf('Closure')) + ->will($this->returnCallback(function ($name, $callback) use ($emitData) { + $emitData && $callback(null); + })); + + return $conn; + } + + private function createTimerMock() + { + return $this->getMockBuilder( + interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface' + )->getMock(); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\Executor') + ->setConstructorArgs(array($this->loop, $this->parser, $this->dumper)) + ->setMethods(array('createConnection')) + ->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php new file mode 100644 index 0000000..70d877e --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php @@ -0,0 +1,126 @@ +hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock(); + $this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + $this->executor = new HostsFileExecutor($this->hosts, $this->fallback); + } + + public function testDoesNotTryToGetIpsForMxQuery() + { + $this->hosts->expects($this->never())->method('getIpsForHost'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoIpsWereFound() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array()); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0)); + } + + public function testReturnsResponseMessageIfIpsWereFound() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1')); + $this->fallback->expects($this->never())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoIpv4Matches() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1')); + $this->fallback->expects($this->once())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0)); + } + + public function testReturnsResponseMessageIfIpv6AddressesWereFound() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1')); + $this->fallback->expects($this->never())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoIpv6Matches() + { + $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1')); + $this->fallback->expects($this->once())->method('query'); + + $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0)); + } + + public function testDoesReturnReverseIpv4Lookup() + { + $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost')); + $this->fallback->expects($this->never())->method('query'); + + $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testFallsBackIfNoReverseIpv4Matches() + { + $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array()); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testDoesReturnReverseIpv6Lookup() + { + $this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost')); + $this->fallback->expects($this->never())->method('query'); + + $this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testFallsBackForInvalidAddress() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testReverseFallsBackForInvalidIpv4Address() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testReverseFallsBackForInvalidLengthIpv6Address() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } + + public function testReverseFallsBackForInvalidHexIpv6Address() + { + $this->hosts->expects($this->never())->method('getHostsForIp'); + $this->fallback->expects($this->once())->method('query'); + + $this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0)); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php b/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php new file mode 100644 index 0000000..83b8934 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php @@ -0,0 +1,64 @@ +assertSame(array(), $recordBag->all()); + } + + /** + * @covers React\Dns\Query\RecordBag + * @test + */ + public function setShouldSetTheValue() + { + $currentTime = 1345656451; + + $recordBag = new RecordBag(); + $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600)); + + $records = $recordBag->all(); + $this->assertCount(1, $records); + $this->assertSame('igor.io', $records[0]->name); + $this->assertSame(Message::TYPE_A, $records[0]->type); + $this->assertSame(Message::CLASS_IN, $records[0]->class); + } + + /** + * @covers React\Dns\Query\RecordBag + * @test + */ + public function setShouldSetManyValues() + { + $currentTime = 1345656451; + + $recordBag = new RecordBag(); + $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132')); + + $records = $recordBag->all(); + $this->assertCount(2, $records); + $this->assertSame('igor.io', $records[0]->name); + $this->assertSame(Message::TYPE_A, $records[0]->type); + $this->assertSame(Message::CLASS_IN, $records[0]->class); + $this->assertSame('178.79.169.131', $records[0]->data); + $this->assertSame('igor.io', $records[1]->name); + $this->assertSame(Message::TYPE_A, $records[1]->type); + $this->assertSame(Message::CLASS_IN, $records[1]->class); + $this->assertSame('178.79.169.132', $records[1]->data); + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php b/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php new file mode 100644 index 0000000..399fbe8 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php @@ -0,0 +1,123 @@ +lookup($query); + + $this->assertInstanceOf('React\Promise\RejectedPromise', $promise); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function storeRecordShouldMakeLookupSucceed() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + + $cache = new RecordCache(new ArrayCache()); + $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise); + $cachedRecords = $this->getPromiseValue($promise); + + $this->assertCount(1, $cachedRecords); + $this->assertSame('178.79.169.131', $cachedRecords[0]->data); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function storeTwoRecordsShouldReturnBoth() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + + $cache = new RecordCache(new ArrayCache()); + $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132')); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise); + $cachedRecords = $this->getPromiseValue($promise); + + $this->assertCount(2, $cachedRecords); + $this->assertSame('178.79.169.131', $cachedRecords[0]->data); + $this->assertSame('178.79.169.132', $cachedRecords[1]->data); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function storeResponseMessageShouldStoreAllAnswerValues() + { + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + + $response = new Message(); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'); + $response->prepare(); + + $cache = new RecordCache(new ArrayCache()); + $cache->storeResponseMessage($query->currentTime, $response); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise); + $cachedRecords = $this->getPromiseValue($promise); + + $this->assertCount(2, $cachedRecords); + $this->assertSame('178.79.169.131', $cachedRecords[0]->data); + $this->assertSame('178.79.169.132', $cachedRecords[1]->data); + } + + /** + * @covers React\Dns\Query\RecordCache + * @test + */ + public function expireShouldExpireDeadRecords() + { + $cachedTime = 1345656451; + $currentTime = $cachedTime + 3605; + + $cache = new RecordCache(new ArrayCache()); + $cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131')); + $cache->expire($currentTime); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime); + $promise = $cache->lookup($query); + + $this->assertInstanceOf('React\Promise\RejectedPromise', $promise); + } + + private function getPromiseValue(PromiseInterface $promise) + { + $capturedValue = null; + + $promise->then(function ($value) use (&$capturedValue) { + $capturedValue = $value; + }); + + return $capturedValue; + } +} diff --git a/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php new file mode 100644 index 0000000..8950f84 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php @@ -0,0 +1,197 @@ +createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnValue($this->expectPromiseOnce())); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldRetryQueryOnTimeout() + { + $response = $this->createStandardResponse(); + + $executor = $this->createExecutorMock(); + $executor + ->expects($this->exactly(2)) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->onConsecutiveCalls( + $this->returnCallback(function ($domain, $query) { + return Promise\reject(new TimeoutException("timeout")); + }), + $this->returnCallback(function ($domain, $query) use ($response) { + return Promise\resolve($response); + }) + )); + + $callback = $this->createCallableMock(); + $callback + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('React\Dns\Model\Message')); + + $errorback = $this->expectCallableNever(); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldStopRetryingAfterSomeAttempts() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->exactly(3)) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($domain, $query) { + return Promise\reject(new TimeoutException("timeout")); + })); + + $callback = $this->expectCallableNever(); + + $errorback = $this->createCallableMock(); + $errorback + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('RuntimeException')); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldForwardNonTimeoutErrors() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($domain, $query) { + return Promise\reject(new \Exception); + })); + + $callback = $this->expectCallableNever(); + + $errorback = $this->createCallableMock(); + $errorback + ->expects($this->once()) + ->method('__invoke') + ->with($this->isInstanceOf('Exception')); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback); + } + + /** + * @covers React\Dns\Query\RetryExecutor + * @test + */ + public function queryShouldCancelQueryOnCancel() + { + $cancelled = 0; + + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) { + $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) { + ++$cancelled; + $reject(new CancellationException('Cancelled')); + }); + + return $deferred->promise(); + }) + ); + + $retryExecutor = new RetryExecutor($executor, 2); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $retryExecutor->query('8.8.8.8', $query); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + + $this->assertEquals(0, $cancelled); + $promise->cancel(); + $this->assertEquals(1, $cancelled); + } + + protected function expectPromiseOnce($return = null) + { + $mock = $this->createPromiseMock(); + $mock + ->expects($this->once()) + ->method('then') + ->will($this->returnValue($return)); + + return $mock; + } + + protected function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } + + protected function createPromiseMock() + { + return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock(); + } + + protected function createStandardResponse() + { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'); + $response->prepare(); + + return $response; + } +} + diff --git a/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php new file mode 100644 index 0000000..0d37fb4 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php @@ -0,0 +1,115 @@ +loop = Factory::create(); + + $this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + + $this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop); + } + + public function testCancellingPromiseWillCancelWrapped() + { + $cancelled = 0; + + $this->wrapped + ->expects($this->once()) + ->method('query') + ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) { + $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) { + ++$cancelled; + $reject(new CancellationException('Cancelled')); + }); + + return $deferred->promise(); + })); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $this->assertEquals(0, $cancelled); + $promise->cancel(); + $this->assertEquals(1, $cancelled); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } + + public function testResolvesPromiseWhenWrappedResolves() + { + $this->wrapped + ->expects($this->once()) + ->method('query') + ->willReturn(Promise\resolve('0.0.0.0')); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + } + + public function testRejectsPromiseWhenWrappedRejects() + { + $this->wrapped + ->expects($this->once()) + ->method('query') + ->willReturn(Promise\reject(new \RuntimeException())); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $promise = $this->executor->query('8.8.8.8:53', $query); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException())); + } + + public function testWrappedWillBeCancelledOnTimeout() + { + $this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop); + + $cancelled = 0; + + $this->wrapped + ->expects($this->once()) + ->method('query') + ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) { + $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) { + ++$cancelled; + $reject(new CancellationException('Cancelled')); + }); + + return $deferred->promise(); + })); + + $callback = $this->expectCallableNever(); + + $errorback = $this->createCallableMock(); + $errorback + ->expects($this->once()) + ->method('__invoke') + ->with($this->logicalAnd( + $this->isInstanceOf('React\Dns\Query\TimeoutException'), + $this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message') + )); + + $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451); + $this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback); + + $this->assertEquals(0, $cancelled); + + $this->loop->run(); + + $this->assertEquals(1, $cancelled); + } +} diff --git a/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php b/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php new file mode 100644 index 0000000..acaeac0 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php @@ -0,0 +1,131 @@ +getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create('8.8.8.8:53', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + } + + /** @test */ + public function createWithoutPortShouldCreateResolverWithDefaultPort() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create('8.8.8.8', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver')); + } + + /** @test */ + public function createCachedShouldCreateResolverWithCachedExecutor() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->createCached('8.8.8.8:53', $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $executor = $this->getResolverPrivateExecutor($resolver); + $this->assertInstanceOf('React\Dns\Query\CachedExecutor', $executor); + $recordCache = $this->getCachedExecutorPrivateMemberValue($executor, 'cache'); + $recordCacheCache = $this->getRecordCachePrivateMemberValue($recordCache, 'cache'); + $this->assertInstanceOf('React\Cache\CacheInterface', $recordCacheCache); + $this->assertInstanceOf('React\Cache\ArrayCache', $recordCacheCache); + } + + /** @test */ + public function createCachedShouldCreateResolverWithCachedExecutorWithCustomCache() + { + $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock(); + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->createCached('8.8.8.8:53', $loop, $cache); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $executor = $this->getResolverPrivateExecutor($resolver); + $this->assertInstanceOf('React\Dns\Query\CachedExecutor', $executor); + $recordCache = $this->getCachedExecutorPrivateMemberValue($executor, 'cache'); + $recordCacheCache = $this->getRecordCachePrivateMemberValue($recordCache, 'cache'); + $this->assertInstanceOf('React\Cache\CacheInterface', $recordCacheCache); + $this->assertSame($cache, $recordCacheCache); + } + + /** + * @test + * @dataProvider factoryShouldAddDefaultPortProvider + */ + public function factoryShouldAddDefaultPort($input, $expected) + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $factory = new Factory(); + $resolver = $factory->create($input, $loop); + + $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver); + $this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver')); + } + + public static function factoryShouldAddDefaultPortProvider() + { + return array( + array('8.8.8.8', '8.8.8.8:53'), + array('1.2.3.4:5', '1.2.3.4:5'), + array('localhost', 'localhost:53'), + array('localhost:1234', 'localhost:1234'), + array('::1', '[::1]:53'), + array('[::1]:53', '[::1]:53') + ); + } + + private function getResolverPrivateExecutor($resolver) + { + $executor = $this->getResolverPrivateMemberValue($resolver, 'executor'); + + // extract underlying executor that may be wrapped in multiple layers of hosts file executors + while ($executor instanceof HostsFileExecutor) { + $reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback'); + $reflector->setAccessible(true); + + $executor = $reflector->getValue($executor); + } + + return $executor; + } + + private function getResolverPrivateMemberValue($resolver, $field) + { + $reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field); + $reflector->setAccessible(true); + return $reflector->getValue($resolver); + } + + private function getCachedExecutorPrivateMemberValue($resolver, $field) + { + $reflector = new \ReflectionProperty('React\Dns\Query\CachedExecutor', $field); + $reflector->setAccessible(true); + return $reflector->getValue($resolver); + } + + private function getRecordCachePrivateMemberValue($resolver, $field) + { + $reflector = new \ReflectionProperty('React\Dns\Query\RecordCache', $field); + $reflector->setAccessible(true); + return $reflector->getValue($resolver); + } +} diff --git a/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php b/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php new file mode 100644 index 0000000..b5175e3 --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php @@ -0,0 +1,100 @@ +createExecutorMock(); + $resolver = new Resolver('8.8.8.8:53', $executor); + + $answers = $resolver->resolveAliases($answers, $name); + + $this->assertEquals($expectedAnswers, $answers); + } + + public function provideAliasedAnswers() + { + return array( + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array('178.79.169.131', '178.79.169.132', '178.79.169.133'), + array( + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'), + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'), + ), + 'igor.io', + ), + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array(), + array( + new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN), + new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN), + ), + 'igor.io', + ), + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'), + new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array('178.79.169.131'), + array( + new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'), + new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'), + new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + ), + 'igor.io', + ), + array( + array('178.79.169.131', '178.79.169.132', '178.79.169.133'), + array( + new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'), + new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'), + new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'), + new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'), + new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'), + new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'), + new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'), + ), + 'igor.io', + ), + ); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php b/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php new file mode 100644 index 0000000..e11509b --- /dev/null +++ b/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php @@ -0,0 +1,129 @@ +createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131'); + + return Promise\resolve($response); + })); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131')); + } + + /** @test */ + public function resolveShouldQueryARecordsAndIgnoreCase() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class); + $response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131'); + + return Promise\resolve($response); + })); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131')); + } + + /** @test */ + public function resolveShouldFilterByName() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + $response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131'); + + return Promise\resolve($response); + })); + + $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException')); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback); + } + + /** @test */ + public function resolveWithNoAnswersShouldThrowException() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + + return Promise\resolve($response); + })); + + $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException')); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback); + } + + /** + * @test + */ + public function resolveWithNoAnswersShouldCallErrbackIfGiven() + { + $executor = $this->createExecutorMock(); + $executor + ->expects($this->once()) + ->method('query') + ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query')) + ->will($this->returnCallback(function ($nameserver, $query) { + $response = new Message(); + $response->header->set('qr', 1); + $response->questions[] = new Record($query->name, $query->type, $query->class); + + return Promise\resolve($response); + })); + + $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException')); + + $resolver = new Resolver('8.8.8.8:53', $executor); + $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback); + } + + private function createExecutorMock() + { + return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/dns/tests/TestCase.php b/assets/php/vendor/react/dns/tests/TestCase.php new file mode 100644 index 0000000..a5a22bf --- /dev/null +++ b/assets/php/vendor/react/dns/tests/TestCase.php @@ -0,0 +1,61 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnceWith($value) + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($value); + + return $mock; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock(); + } + + public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) + { + if (method_exists($this, 'expectException')) { + // PHPUnit 5 + $this->expectException($exception); + if ($exceptionMessage !== '') { + $this->expectExceptionMessage($exceptionMessage); + } + if ($exceptionCode !== null) { + $this->expectExceptionCode($exceptionCode); + } + } else { + // legacy PHPUnit 4 + parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); + } + } +} diff --git a/assets/php/vendor/react/event-loop/.gitignore b/assets/php/vendor/react/event-loop/.gitignore new file mode 100644 index 0000000..81b9258 --- /dev/null +++ b/assets/php/vendor/react/event-loop/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor diff --git a/assets/php/vendor/react/event-loop/.travis.yml b/assets/php/vendor/react/event-loop/.travis.yml new file mode 100644 index 0000000..7af713a --- /dev/null +++ b/assets/php/vendor/react/event-loop/.travis.yml @@ -0,0 +1,39 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm # ignore errors, see below + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + allow_failures: + - php: hhvm + +sudo: false + +addons: + apt: + packages: + - libevent-dev # Used by 'event' and 'libevent' PHP extensions + +cache: + directories: + - $HOME/.composer/cache/files + +install: + - ./travis-init.sh + - composer install + +script: + - ./vendor/bin/phpunit --coverage-text diff --git a/assets/php/vendor/react/event-loop/CHANGELOG.md b/assets/php/vendor/react/event-loop/CHANGELOG.md new file mode 100644 index 0000000..c291840 --- /dev/null +++ b/assets/php/vendor/react/event-loop/CHANGELOG.md @@ -0,0 +1,316 @@ +# Changelog + +## 0.5.1 (2018-04-09) + +* New loop: ExtEvLoop (PECL ext-ev) (#148 by @kaduev13) + +## 0.5.0 (2018-04-05) + +A major feature release with a significant documentation overhaul and long overdue API cleanup! + +This update involves a number of BC breaks due to dropped support for deprecated +functionality. We've tried hard to avoid BC breaks where possible and minimize +impact otherwise. We expect that most consumers of this package will actually +not be affected by any BC breaks, see below for more details. + +We realize that the changes listed below may seem overwhelming, but we've tried +to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP +components are already compatible and support both this new release as well as +providing backwards compatibility with the last release. + +* Feature / BC break: Add support for signal handling via new + `LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods. + (#104 by @WyriHaximus and #111 and #150 by @clue) + + ```php + $loop->addSignal(SIGINT, function () { + echo 'CTRL-C'; + }); + ``` + +* Feature: Significant documentation updates for `LoopInterface` and `Factory`. + (#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor) + +* Feature: Add examples to ease getting started + (#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor) + +* Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time + and high precision timers with millisecond accuracy or below. + (#130 and #157 by @clue) + +* Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners + and stream buffers and allow throwing Exception if stream resource is not supported. + (#129 and #158 by @clue) + +* Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed. + (#153 by @WyriHaximus) + +* Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM + and remove all `callable` type hints for consistency reasons. + (#141 and #151 by @clue) + +* BC break: Documentation for timer API and clean up unneeded timer API. + (#102 by @clue) + + Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead: + + ```php + // old (method invoked on timer instance) + $timer->cancel(); + + // already supported before: invoke method on loop instance + $loop->cancelTimer($timer); + ``` + + Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`, + use closure binding to add arbitrary data to timer instead: + + ```php + // old (limited setData() and getData() only allows single variable) + $name = 'Tester'; + $timer = $loop->addTimer(1.0, function ($timer) { + echo 'Hello ' . $timer->getData() . PHP_EOL; + }); + $timer->setData($name); + + // already supported before: closure binding allows any number of variables + $name = 'Tester'; + $loop->addTimer(1.0, function () use ($name) { + echo 'Hello ' . $name . PHP_EOL; + }); + ``` + + Remove unneeded `TimerInterface::getLoop()`, use closure binding instead: + + ```php + // old (getLoop() called on timer instance) + $loop->addTimer(0.1, function ($timer) { + $timer->getLoop()->stop(); + }); + + // already supported before: use closure binding as usual + $loop->addTimer(0.1, function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::isTimerActive()` and + `TimerInterface::isActive()` to reduce API surface. + (#133 by @clue) + + ```php + // old (method on timer instance or on loop instance) + $timer->isActive(); + $loop->isTimerActive($timer); + ``` + +* BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`. + (#138 by @WyriHaximus) + + ```php + // old (notice obsolete "Timer" namespace) + assert($timer instanceof React\EventLoop\Timer\TimerInterface); + + // new + assert($timer instanceof React\EventLoop\TimerInterface); + ``` + +* BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`), + use `LoopInterface::futureTick()` instead. + (#30 by @clue) + + ```php + // old (removed) + $loop->nextTick(function () { + echo 'tick'; + }); + + // already supported before + $loop->futureTick(function () { + echo 'tick'; + }); + ``` + +* BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()` + (and fix internal cyclic dependency). + (#103 by @clue) + + ```php + // old ($loop gets passed by default) + $loop->futureTick(function ($loop) { + $loop->stop(); + }); + + // already supported before: use closure binding as usual + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::tick()`. + (#72 by @jsor) + + ```php + // old (removed) + $loop->tick(); + + // suggested work around for testing purposes only + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Documentation for advanced stream API and clean up unneeded stream API. + (#110 by @clue) + + Remove unneeded `$loop` argument for `LoopInterface::addReadStream()` + and `LoopInterface::addWriteStream()`, use closure binding instead: + + ```php + // old ($loop gets passed by default) + $loop->addReadStream($stream, function ($stream, $loop) { + $loop->removeReadStream($stream); + }); + + // already supported before: use closure binding as usual + $loop->addReadStream($stream, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::removeStream()` method, + use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead. + (#118 by @clue) + + ```php + // old + $loop->removeStream($stream); + + // already supported before + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); + ``` + +* BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop` + for consistent naming for event loop implementations. + (#128 by @clue) + +* BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop` + and make its `FEATURE_FDS` enabled by default. + (#156 by @WyriHaximus) + +* BC break: Mark all classes as final to discourage inheritance. + (#131 by @clue) + +* Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount) + (#123 by @clue) + +* Fix: Ensure large timer interval does not overflow on 32bit systems + (#132 by @clue) + +* Fix: Fix separately removing readable and writable side of stream when closing + (#139 by @clue) + +* Fix: Properly clean up event watchers for `ext-event` and `ext-libev` + (#149 by @clue) + +* Fix: Minor code cleanup and remove unneeded references + (#145 by @seregazhuk) + +* Fix: Discourage outdated `ext-libevent` on PHP 7 + (#62 by @cboden) + +* Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5, + lock Travis distro so new defaults will not break the build, + improve test suite to be less fragile and increase test timeouts, + test against PHP 7.2 and reduce fwrite() call length to one chunk. + (#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik) + +* A number of changes were originally planned for this release but have been backported + to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93 + +## 0.4.3 (2017-04-27) + +* Bug fix: Bugfix in the usage sample code #57 (@dandelionred) +* Improvement: Remove branch-alias definition #53 (@WyriHaximus) +* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd) +* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder) +* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley) +* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue) +* Improvement: Travis improvements (backported from #74) #75 (@clue) +* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder) +* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder) +* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek) +* Improvement: Readme cleanup #89 (@jsor) +* Improvement: Restructure and improve README #90 (@jsor) +* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor) + +## 0.4.2 (2016-03-07) + +* Bug fix: No longer error when signals sent to StreamSelectLoop +* Support HHVM and PHP7 (@ondrejmirtes, @cebe) +* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades) +* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino) + +## 0.4.1 (2014-04-13) + +* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue) +* Bug fix: v0.3.4 changes merged for v0.4.1 + +## 0.4.0 (2014-02-02) + +* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc) +* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc) +* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc) +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: New method: `EventLoopInterface::nextTick()` +* BC break: New method: `EventLoopInterface::futureTick()` +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 + +## 0.3.5 (2016-12-28) + +This is a compatibility release that eases upgrading to the v0.4 release branch. +You should consider upgrading to the v0.4 release branch. + +* Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4 + (#47 by @clue) + +## 0.3.4 (2014-03-30) + +* Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25) + +## 0.3.3 (2013-07-08) + +* Bug fix: No error on removing non-existent streams (@clue) +* Bug fix: Do not silently remove feof listeners in `LibEvLoop` + +## 0.3.0 (2013-04-14) + +* BC break: New timers API (@nrk) +* BC break: Remove check on return value from stream callbacks (@nrk) + +## 0.2.7 (2013-01-05) + +* Bug fix: Fix libevent timers with PHP 5.3 +* Bug fix: Fix libevent timer cancellation (@nrk) + +## 0.2.6 (2012-12-26) + +* Bug fix: Plug memory issue in libevent timers (@cameronjacobson) +* Bug fix: Correctly pause LibEvLoop on stop() + +## 0.2.3 (2012-11-14) + +* Feature: LibEvLoop, integration of `php-libev` + +## 0.2.0 (2012-09-10) + +* Version bump + +## 0.1.1 (2012-07-12) + +* Version bump + +## 0.1.0 (2012-07-11) + +* First tagged release diff --git a/assets/php/vendor/react/event-loop/LICENSE b/assets/php/vendor/react/event-loop/LICENSE new file mode 100644 index 0000000..a808108 --- /dev/null +++ b/assets/php/vendor/react/event-loop/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Igor Wiedler, Chris Boden + +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/assets/php/vendor/react/event-loop/README.md b/assets/php/vendor/react/event-loop/README.md new file mode 100644 index 0000000..207e7f4 --- /dev/null +++ b/assets/php/vendor/react/event-loop/README.md @@ -0,0 +1,702 @@ +# EventLoop Component + +[![Build Status](https://travis-ci.org/reactphp/event-loop.svg?branch=master)](https://travis-ci.org/reactphp/event-loop) + +[ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. + +In order for async based libraries to be interoperable, they need to use the +same event loop. This component provides a common `LoopInterface` that any +library can target. This allows them to be used in the same loop, with one +single [`run()`](#run) call that is controlled by the user. + +**Table of Contents** + +* [Quickstart example](#quickstart-example) +* [Usage](#usage) + * [Factory](#factory) + * [create()](#create) + * [Loop implementations](#loop-implementations) + * [StreamSelectLoop](#streamselectloop) + * [ExtEventLoop](#exteventloop) + * [ExtLibeventLoop](#extlibeventloop) + * [ExtLibevLoop](#extlibevloop) + * [ExtEvLoop](#extevloop) + * [LoopInterface](#loopinterface) + * [run()](#run) + * [stop()](#stop) + * [addTimer()](#addtimer) + * [addPeriodicTimer()](#addperiodictimer) + * [cancelTimer()](#canceltimer) + * [futureTick()](#futuretick) + * [addSignal()](#addsignal) + * [removeSignal()](#removesignal) + * [addReadStream()](#addreadstream) + * [addWriteStream()](#addwritestream) + * [removeReadStream()](#removereadstream) + * [removeWriteStream()](#removewritestream) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [More](#more) + +## Quickstart example + +Here is an async HTTP server built with just the event loop. + +```php +$loop = React\EventLoop\Factory::create(); + +$server = stream_socket_server('tcp://127.0.0.1:8080'); +stream_set_blocking($server, false); + +$loop->addReadStream($server, function ($server) use ($loop) { + $conn = stream_socket_accept($server); + $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; + $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) { + $written = fwrite($conn, $data); + if ($written === strlen($data)) { + fclose($conn); + $loop->removeWriteStream($conn); + } else { + $data = substr($data, $written); + } + }); +}); + +$loop->addPeriodicTimer(5, function () { + $memory = memory_get_usage() / 1024; + $formatted = number_format($memory, 3).'K'; + echo "Current memory usage: {$formatted}\n"; +}); + +$loop->run(); +``` + +See also the [examples](examples). + +## Usage + +Typical applications use a single event loop which is created at the beginning +and run at the end of the program. + +```php +// [1] +$loop = React\EventLoop\Factory::create(); + +// [2] +$loop->addPeriodicTimer(1, function () { + echo "Tick\n"; +}); + +$stream = new React\Stream\ReadableResourceStream( + fopen('file.txt', 'r'), + $loop +); + +// [3] +$loop->run(); +``` + +1. The loop instance is created at the beginning of the program. A convenience + factory [`React\EventLoop\Factory::create()`](#create) is provided by this library which + picks the best available [loop implementation](#loop-implementations). +2. The loop instance is used directly or passed to library and application code. + In this example, a periodic timer is registered with the event loop which + simply outputs `Tick` every second and a + [readable stream](https://github.com/reactphp/stream#readableresourcestream) + is created by using ReactPHP's + [stream component](https://github.com/reactphp/stream) for demonstration + purposes. +3. The loop is run with a single [`$loop->run()`](#run) call at the end of the program. + +### Factory + +The `Factory` class exists as a convenient way to pick the best available +[event loop implementation](#loop-implementations). + +#### create() + +The `create(): LoopInterface` method can be used to create a new event loop +instance: + +```php +$loop = React\EventLoop\Factory::create(); +``` + +This method always returns an instance implementing [`LoopInterface`](#loopinterface), +the actual [event loop implementation](#loop-implementations) is an implementation detail. + +This method should usually only be called once at the beginning of the program. + +### Loop implementations + +In addition to the [`LoopInterface`](#loopinterface), there are a number of +event loop implementations provided. + +All of the event loops support these features: + +* File descriptor polling +* One-off timers +* Periodic timers +* Deferred execution on future loop tick + +For most consumers of this package, the underlying event loop implementation is +an implementation detail. +You should use the [`Factory`](#factory) to automatically create a new instance. + +Advanced! If you explicitly need a certain event loop implementation, you can +manually instantiate one of the following classes. +Note that you may have to install the required PHP extensions for the respective +event loop implementation first or they will throw a `BadMethodCallException` on creation. + +#### StreamSelectLoop + +A `stream_select()` based event loop. + +This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) +function and is the only implementation which works out of the box with PHP. + +This event loop works out of the box on PHP 5.3 through PHP 7+ and HHVM. +This means that no installation is required and this library works on all +platforms and supported PHP versions. +Accordingly, the [`Factory`](#factory) will use this event loop by default if +you do not install any of the event loop extensions listed below. + +Under the hood, it does a simple `select` system call. +This system call is limited to the maximum file descriptor number of +`FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)` +(`m` being the maximum file descriptor number passed). +This means that you may run into issues when handling thousands of streams +concurrently and you may want to look into using one of the alternative +event loop implementations listed below in this case. +If your use case is among the many common use cases that involve handling only +dozens or a few hundred streams at once, then this event loop implementation +performs really well. + +If you want to use signal handling (see also [`addSignal()`](#addsignal) below), +this event loop implementation requires `ext-pcntl`. +This extension is only available for Unix-like platforms and does not support +Windows. +It is commonly installed as part of many PHP distributions. +If this extension is missing (or you're running on Windows), signal handling is +not supported and throws a `BadMethodCallException` instead. + +This event loop is known to rely on wall-clock time to schedule future +timers, because a monotonic time source is not available in PHP by default. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s and then adjust +your system time forward by 20s, the timer may trigger in 10s. +See also [`addTimer()`](#addtimer) for more details. + +#### ExtEventLoop + +An `ext-event` based event loop. + +This uses the [`event` PECL extension](https://pecl.php.net/package/event). +It supports the same backends as libevent. + +This loop is known to work with PHP 5.4 through PHP 7+. + +#### ExtEvLoop + +An `ext-ev` based event loop. + +This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev), that +provides an interface to `libev` library. + +This loop is known to work with PHP 5.4 through PHP 7+. + + +#### ExtLibeventLoop + +An `ext-libevent` based event loop. + +This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent). +`libevent` itself supports a number of system-specific backends (epoll, kqueue). + +This event loop does only work with PHP 5. +An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for +PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s. +To reiterate: Using this event loop on PHP 7 is not recommended. +Accordingly, the [`Factory`](#factory) will not try to use this event loop on +PHP 7. + +This event loop is known to trigger a readable listener only if +the stream *becomes* readable (edge-triggered) and may not trigger if the +stream has already been readable from the beginning. +This also implies that a stream may not be recognized as readable when data +is still left in PHP's internal stream buffers. +As such, it's recommended to use `stream_set_read_buffer($stream, 0);` +to disable PHP's internal read buffer in this case. +See also [`addReadStream()`](#addreadstream) for more details. + +#### ExtLibevLoop + +An `ext-libev` based event loop. + +This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev). +It supports the same backends as libevent. + +This loop does only work with PHP 5. +An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8) +to happen any time soon. + +### LoopInterface + +#### run() + +The `run(): void` method can be used to +run the event loop until there are no more tasks to perform. + +For many applications, this method is the only directly visible +invocation on the event loop. +As a rule of thumb, it is usally recommended to attach everything to the +same loop instance and then run the loop once at the bottom end of the +application. + +```php +$loop->run(); +``` + +This method will keep the loop running until there are no more tasks +to perform. In other words: This method will block until the last +timer, stream and/or signal has been removed. + +Likewise, it is imperative to ensure the application actually invokes +this method once. Adding listeners to the loop and missing to actually +run it will result in the application exiting without actually waiting +for any of the attached listeners. + +This method MUST NOT be called while the loop is already running. +This method MAY be called more than once after it has explicity been +[`stop()`ped](#stop) or after it automatically stopped because it +previously did no longer have anything to do. + +#### stop() + +The `stop(): void` method can be used to +instruct a running event loop to stop. + +This method is considered advanced usage and should be used with care. +As a rule of thumb, it is usually recommended to let the loop stop +only automatically when it no longer has anything to do. + +This method can be used to explicitly instruct the event loop to stop: + +```php +$loop->addTimer(3.0, function () use ($loop) { + $loop->stop(); +}); +``` + +Calling this method on a loop instance that is not currently running or +on a loop instance that has already been stopped has no effect. + +#### addTimer() + +The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to +enqueue a callback to be invoked once after the given interval. + +The timer callback function MUST be able to accept a single parameter, +the timer instance as also returned by this method or you MAY use a +function which has no parameters at all. + +The timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure +the callback will be invoked only once after the given interval. +You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. + +```php +$loop->addTimer(0.8, function () { + echo 'world!' . PHP_EOL; +}); + +$loop->addTimer(0.3, function () { + echo 'hello '; +}); +``` + +See also [example #1](examples). + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +function hello($name, LoopInterface $loop) +{ + $loop->addTimer(1.0, function () use ($name) { + echo "hello $name\n"; + }); +} + +hello('Tester', $loop); +``` + +This interface does not enforce any particular timer resolution, so +special care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Event loop implementations SHOULD work on +a best effort basis and SHOULD provide at least millisecond accuracy +unless otherwise noted. Many existing event loop implementations are +known to provide microsecond accuracy, but it's generally not recommended +to rely on this high precision. + +Similarly, the execution order of timers scheduled to execute at the +same time (within its possible accuracy) is not guaranteed. + +This interface suggests that event loop implementations SHOULD use a +monotonic time source if available. Given that a monotonic time source is +not available on PHP by default, event loop implementations MAY fall back +to using wall-clock time. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s and then adjust +your system time forward by 20s, the timer SHOULD still trigger in 30s. +See also [event loop implementations](#loop-implementations) for more details. + +#### addPeriodicTimer() + +The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to +enqueue a callback to be invoked repeatedly after the given interval. + +The timer callback function MUST be able to accept a single parameter, +the timer instance as also returned by this method or you MAY use a +function which has no parameters at all. + +The timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +Unlike [`addTimer()`](#addtimer), this method will ensure the the +callback will be invoked infinitely after the given interval or until you +invoke [`cancelTimer`](#canceltimer). + +```php +$timer = $loop->addPeriodicTimer(0.1, function () { + echo 'tick!' . PHP_EOL; +}); + +$loop->addTimer(1.0, function () use ($loop, $timer) { + $loop->cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); +``` + +See also [example #2](examples). + +If you want to limit the number of executions, you can bind +arbitrary data to a callback closure like this: + +```php +function hello($name, LoopInterface $loop) +{ + $n = 3; + $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + if ($n > 0) { + --$n; + echo "hello $name\n"; + } else { + $loop->cancelTimer($timer); + } + }); +} + +hello('Tester', $loop); +``` + +This interface does not enforce any particular timer resolution, so +special care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Event loop implementations SHOULD work on +a best effort basis and SHOULD provide at least millisecond accuracy +unless otherwise noted. Many existing event loop implementations are +known to provide microsecond accuracy, but it's generally not recommended +to rely on this high precision. + +Similarly, the execution order of timers scheduled to execute at the +same time (within its possible accuracy) is not guaranteed. + +This interface suggests that event loop implementations SHOULD use a +monotonic time source if available. Given that a monotonic time source is +not available on PHP by default, event loop implementations MAY fall back +to using wall-clock time. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s and then adjust +your system time forward by 20s, the timer SHOULD still trigger in 30s. +See also [event loop implementations](#loop-implementations) for more details. + +Additionally, periodic timers may be subject to timer drift due to +re-scheduling after each invocation. As such, it's generally not +recommended to rely on this for high precision intervals with millisecond +accuracy or below. + +#### cancelTimer() + +The `cancelTimer(TimerInterface $timer): void` method can be used to +cancel a pending timer. + +See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + +Calling this method on a timer instance that has not been added to this +loop instance or on a timer that has already been cancelled has no effect. + +#### futureTick() + +The `futureTick(callable $listener): void` method can be used to +schedule a callback to be invoked on a future tick of the event loop. + +This works very much similar to timers with an interval of zero seconds, +but does not require the overhead of scheduling a timer queue. + +The tick callback function MUST be able to accept zero parameters. + +The tick callback function MUST NOT throw an `Exception`. +The return value of the tick callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +function hello($name, LoopInterface $loop) +{ + $loop->futureTick(function () use ($name) { + echo "hello $name\n"; + }); +} + +hello('Tester', $loop); +``` + +Unlike timers, tick callbacks are guaranteed to be executed in the order +they are enqueued. +Also, once a callback is enqueued, there's no way to cancel this operation. + +This is often used to break down bigger tasks into smaller steps (a form +of cooperative multitasking). + +```php +$loop->futureTick(function () { + echo 'b'; +}); +$loop->futureTick(function () { + echo 'c'; +}); +echo 'a'; +``` + +See also [example #3](examples). + +#### addSignal() + +The `addSignal(int $signal, callable $listener): void` method can be used to +register a listener to be notified when a signal has been caught by this process. + +This is useful to catch user interrupt signals or shutdown signals from +tools like `supervisor` or `systemd`. + +The listener callback function MUST be able to accept a single parameter, +the signal added by this method or you MAY use a function which +has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +```php +$loop->addSignal(SIGINT, function (int $signal) { + echo 'Caught user interrupt signal' . PHP_EOL; +}); +``` + +See also [example #4](examples). + +Signaling is only available on Unix-like platform, Windows isn't +supported due to operating system limitations. +This method may throw a `BadMethodCallException` if signals aren't +supported on this platform, for example when required extensions are +missing. + +**Note: A listener can only be added once to the same signal, any +attempts to add it more then once will be ignored.** + +#### removeSignal() + +The `removeSignal(int $signal, callable $listener): void` method can be used to +remove a previously added signal listener. + +```php +$loop->removeSignal(SIGINT, $listener); +``` + +Any attempts to remove listeners that aren't registered will be ignored. + +#### addReadStream() + +> Advanced! Note that this low-level API is considered advanced usage. + Most use cases should probably use the higher-level + [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface) + instead. + +The `addReadStream(resource $stream, callable $callback): void` method can be used to +register a listener to be notified when a stream is ready to read. + +The first parameter MUST be a valid stream resource that supports +checking whether it is ready to read by this loop implementation. +A single stream resource MUST NOT be added more than once. +Instead, either call [`removeReadStream()`](#removereadstream) first or +react to this event with a single listener and then dispatch from this +listener. This method MAY throw an `Exception` if the given resource type +is not supported by this loop implementation. + +The listener callback function MUST be able to accept a single parameter, +the stream resource added by this method or you MAY use a function which +has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +$loop->addReadStream($stream, function ($stream) use ($name) { + echo $name . ' said: ' . fread($stream); +}); +``` + +See also [example #11](examples). + +You can invoke [`removeReadStream()`](#removereadstream) to remove the +read event listener for this stream. + +The execution order of listeners when multiple streams become ready at +the same time is not guaranteed. + +Some event loop implementations are known to only trigger the listener if +the stream *becomes* readable (edge-triggered) and may not trigger if the +stream has already been readable from the beginning. +This also implies that a stream may not be recognized as readable when data +is still left in PHP's internal stream buffers. +As such, it's recommended to use `stream_set_read_buffer($stream, 0);` +to disable PHP's internal read buffer in this case. + +#### addWriteStream() + +> Advanced! Note that this low-level API is considered advanced usage. + Most use cases should probably use the higher-level + [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) + instead. + +The `addWriteStream(resource $stream, callable $callback): void` method can be used to +register a listener to be notified when a stream is ready to write. + +The first parameter MUST be a valid stream resource that supports +checking whether it is ready to write by this loop implementation. +A single stream resource MUST NOT be added more than once. +Instead, either call [`removeWriteStream()`](#removewritestream) first or +react to this event with a single listener and then dispatch from this +listener. This method MAY throw an `Exception` if the given resource type +is not supported by this loop implementation. + +The listener callback function MUST be able to accept a single parameter, +the stream resource added by this method or you MAY use a function which +has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +$loop->addWriteStream($stream, function ($stream) use ($name) { + fwrite($stream, 'Hello ' . $name); +}); +``` + +See also [example #12](examples). + +You can invoke [`removeWriteStream()`](#removewritestream) to remove the +write event listener for this stream. + +The execution order of listeners when multiple streams become ready at +the same time is not guaranteed. + +#### removeReadStream() + +The `removeReadStream(resource $stream): void` method can be used to +remove the read event listener for the given stream. + +Removing a stream from the loop that has already been removed or trying +to remove a stream that was never added or is invalid has no effect. + +#### removeWriteStream() + +The `removeWriteStream(resource $stream): void` method can be used to +remove the write event listener for the given stream. + +Removing a stream from the loop that has already been removed or trying +to remove a stream that was never added or is invalid has no effect. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require react/event-loop:^0.5.1 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and +HHVM. +It's *highly recommended to use PHP 7+* for this project. + +Installing any of the event loop extensions is suggested, but entirely optional. +See also [event loop implementations](#loop-implementations) for more details. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +## License + +MIT, see [LICENSE file](LICENSE). + +## More + +* See our [Stream component](https://github.com/reactphp/stream) for more + information on how streams are used in real-world applications. +* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the + [dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents) + for a list of packages that use the EventLoop in real-world applications. diff --git a/assets/php/vendor/react/event-loop/composer.json b/assets/php/vendor/react/event-loop/composer.json new file mode 100644 index 0000000..24974ec --- /dev/null +++ b/assets/php/vendor/react/event-loop/composer.json @@ -0,0 +1,21 @@ +{ + "name": "react/event-loop", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": ["event-loop", "asynchronous"], + "license": "MIT", + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" + }, + "suggest": { + "ext-event": "~1.0 for ExtEventLoop", + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src" + } + } +} diff --git a/assets/php/vendor/react/event-loop/examples/01-timers.php b/assets/php/vendor/react/event-loop/examples/01-timers.php new file mode 100644 index 0000000..e6107e4 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/01-timers.php @@ -0,0 +1,15 @@ +addTimer(0.8, function () { + echo 'world!' . PHP_EOL; +}); + +$loop->addTimer(0.3, function () { + echo 'hello '; +}); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/02-periodic.php b/assets/php/vendor/react/event-loop/examples/02-periodic.php new file mode 100644 index 0000000..5e138a6 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/02-periodic.php @@ -0,0 +1,16 @@ +addPeriodicTimer(0.1, function () { + echo 'tick!' . PHP_EOL; +}); + +$loop->addTimer(1.0, function () use ($loop, $timer) { + $loop->cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/03-ticks.php b/assets/php/vendor/react/event-loop/examples/03-ticks.php new file mode 100644 index 0000000..3f36c6d --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/03-ticks.php @@ -0,0 +1,15 @@ +futureTick(function () { + echo 'b'; +}); +$loop->futureTick(function () { + echo 'c'; +}); +echo 'a'; + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/04-signals.php b/assets/php/vendor/react/event-loop/examples/04-signals.php new file mode 100644 index 0000000..90b6898 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/04-signals.php @@ -0,0 +1,19 @@ +addSignal(SIGINT, $func = function ($signal) use ($loop, &$func) { + echo 'Signal: ', (string)$signal, PHP_EOL; + $loop->removeSignal(SIGINT, $func); +}); + +echo 'Listening for SIGINT. Use "kill -SIGINT ' . getmypid() . '" or CTRL+C' . PHP_EOL; + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/11-consume-stdin.php b/assets/php/vendor/react/event-loop/examples/11-consume-stdin.php new file mode 100644 index 0000000..2a77245 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/11-consume-stdin.php @@ -0,0 +1,30 @@ +addReadStream(STDIN, function ($stream) use ($loop) { + $chunk = fread($stream, 64 * 1024); + + // reading nothing means we reached EOF + if ($chunk === '') { + $loop->removeReadStream($stream); + stream_set_blocking($stream, true); + fclose($stream); + return; + } + + echo strlen($chunk) . ' bytes' . PHP_EOL; +}); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/12-generate-yes.php b/assets/php/vendor/react/event-loop/examples/12-generate-yes.php new file mode 100644 index 0000000..ebc2beb --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/12-generate-yes.php @@ -0,0 +1,41 @@ +addWriteStream(STDOUT, function ($stdout) use ($loop, &$data) { + // try to write data + $r = fwrite($stdout, $data); + + // nothing could be written despite being writable => closed + if ($r === 0) { + $loop->removeWriteStream($stdout); + fclose($stdout); + stream_set_blocking($stdout, true); + fwrite(STDERR, 'Stopped because STDOUT closed' . PHP_EOL); + + return; + } + + // implement a very simple ring buffer, unless everything has been written at once: + // everything written in this iteration will be appended for next iteration + if (isset($data[$r])) { + $data = substr($data, $r) . substr($data, 0, $r); + } +}); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/13-http-client-blocking.php b/assets/php/vendor/react/event-loop/examples/13-http-client-blocking.php new file mode 100644 index 0000000..a2dde55 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/13-http-client-blocking.php @@ -0,0 +1,35 @@ +addReadStream($stream, function ($stream) use ($loop) { + $chunk = fread($stream, 64 * 1024); + + // reading nothing means we reached EOF + if ($chunk === '') { + echo '[END]' . PHP_EOL; + $loop->removeReadStream($stream); + fclose($stream); + return; + } + + echo $chunk; +}); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/14-http-client-async.php b/assets/php/vendor/react/event-loop/examples/14-http-client-async.php new file mode 100644 index 0000000..c82c988 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/14-http-client-async.php @@ -0,0 +1,63 @@ +addPeriodicTimer(0.01, function () { + echo '.'; +}); + +// wait for connection success/error +$loop->addWriteStream($stream, function ($stream) use ($loop, $timer) { + $loop->removeWriteStream($stream); + $loop->cancelTimer($timer); + + // check for socket error (connection rejected) + if (stream_socket_get_name($stream, true) === false) { + echo '[unable to connect]' . PHP_EOL; + exit(1); + } else { + echo '[connected]' . PHP_EOL; + } + + // send HTTP request + fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n"); + + // wait for HTTP response + $loop->addReadStream($stream, function ($stream) use ($loop) { + $chunk = fread($stream, 64 * 1024); + + // reading nothing means we reached EOF + if ($chunk === '') { + echo '[END]' . PHP_EOL; + $loop->removeReadStream($stream); + fclose($stream); + return; + } + + echo $chunk; + }); +}); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/21-http-server.php b/assets/php/vendor/react/event-loop/examples/21-http-server.php new file mode 100644 index 0000000..89520ce --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/21-http-server.php @@ -0,0 +1,36 @@ +addReadStream($server, function ($server) use ($loop) { + $conn = stream_socket_accept($server); + $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; + $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) { + $written = fwrite($conn, $data); + if ($written === strlen($data)) { + fclose($conn); + $loop->removeWriteStream($conn); + } else { + $data = substr($data, $written); + } + }); +}); + +$loop->addPeriodicTimer(5, function () { + $memory = memory_get_usage() / 1024; + $formatted = number_format($memory, 3).'K'; + echo "Current memory usage: {$formatted}\n"; +}); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/91-benchmark-ticks.php b/assets/php/vendor/react/event-loop/examples/91-benchmark-ticks.php new file mode 100644 index 0000000..3f4690b --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/91-benchmark-ticks.php @@ -0,0 +1,15 @@ +futureTick(function () { }); +} + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/92-benchmark-timers.php b/assets/php/vendor/react/event-loop/examples/92-benchmark-timers.php new file mode 100644 index 0000000..e2e02e4 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/92-benchmark-timers.php @@ -0,0 +1,15 @@ +addTimer(0, function () { }); +} + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/93-benchmark-ticks-delay.php b/assets/php/vendor/react/event-loop/examples/93-benchmark-ticks-delay.php new file mode 100644 index 0000000..95ee78c --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/93-benchmark-ticks-delay.php @@ -0,0 +1,22 @@ + 0) { + --$ticks; + //$loop->addTimer(0, $tick); + $loop->futureTick($tick); + } else { + echo 'done'; + } +}; + +$tick(); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/94-benchmark-timers-delay.php b/assets/php/vendor/react/event-loop/examples/94-benchmark-timers-delay.php new file mode 100644 index 0000000..2d6cfa2 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/94-benchmark-timers-delay.php @@ -0,0 +1,22 @@ + 0) { + --$ticks; + //$loop->futureTick($tick); + $loop->addTimer(0, $tick); + } else { + echo 'done'; + } +}; + +$tick(); + +$loop->run(); diff --git a/assets/php/vendor/react/event-loop/examples/95-benchmark-memory.php b/assets/php/vendor/react/event-loop/examples/95-benchmark-memory.php new file mode 100644 index 0000000..084c404 --- /dev/null +++ b/assets/php/vendor/react/event-loop/examples/95-benchmark-memory.php @@ -0,0 +1,67 @@ +addTimer($t, function () use ($loop) { + $loop->stop(); + }); + +} + +$loop->addPeriodicTimer(0.001, function () use (&$runs, $loop) { + $runs++; + + $loop->addPeriodicTimer(1, function (TimerInterface $timer) use ($loop) { + $loop->cancelTimer($timer); + }); +}); + +$loop->addPeriodicTimer($r, function () use (&$runs) { + $kmem = round(memory_get_usage() / 1024); + $kmemReal = round(memory_get_usage(true) / 1024); + echo "Runs:\t\t\t$runs\n"; + echo "Memory (internal):\t$kmem KiB\n"; + echo "Memory (real):\t\t$kmemReal KiB\n"; + echo str_repeat('-', 50), "\n"; +}); + +echo "PHP Version:\t\t", phpversion(), "\n"; +echo "Loop\t\t\t", get_class($loop), "\n"; +echo "Time\t\t\t", date('r'), "\n"; + +echo str_repeat('-', 50), "\n"; + +$beginTime = time(); +$loop->run(); +$endTime = time(); +$timeTaken = $endTime - $beginTime; + +echo "PHP Version:\t\t", phpversion(), "\n"; +echo "Loop\t\t\t", get_class($loop), "\n"; +echo "Time\t\t\t", date('r'), "\n"; +echo "Time taken\t\t", $timeTaken, " seconds\n"; +echo "Runs per second\t\t", round($runs / $timeTaken), "\n"; diff --git a/assets/php/vendor/react/event-loop/phpunit.xml.dist b/assets/php/vendor/react/event-loop/phpunit.xml.dist new file mode 100644 index 0000000..cba6d4d --- /dev/null +++ b/assets/php/vendor/react/event-loop/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/assets/php/vendor/react/event-loop/src/ExtEvLoop.php b/assets/php/vendor/react/event-loop/src/ExtEvLoop.php new file mode 100644 index 0000000..74db6d0 --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/ExtEvLoop.php @@ -0,0 +1,252 @@ +loop = new EvLoop(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timers = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + } + + public function addReadStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->readStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::READ, $callback); + $this->readStreams[$key] = $event; + } + + /** + * @param resource $stream + * @param callable $listener + * + * @return \Closure + */ + private function getStreamListenerClosure($stream, $listener) + { + return function () use ($stream, $listener) { + call_user_func($listener, $stream); + }; + } + + public function addWriteStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->writeStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::WRITE, $callback); + $this->writeStreams[$key] = $event; + } + + public function removeReadStream($stream) + { + $key = (int)$stream; + + if (!isset($this->readStreams[$key])) { + return; + } + + $this->readStreams[$key]->stop(); + unset($this->readStreams[$key]); + } + + public function removeWriteStream($stream) + { + $key = (int)$stream; + + if (!isset($this->writeStreams[$key])) { + return; + } + + $this->writeStreams[$key]->stop(); + unset($this->writeStreams[$key]); + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $that = $this; + $timers = $this->timers; + $callback = function () use ($timer, $timers, $that) { + call_user_func($timer->getCallback(), $timer); + + if ($timers->contains($timer)) { + $that->cancelTimer($timer); + } + }; + + $event = $this->loop->timer($timer->getInterval(), 0.0, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + }; + + $event = $this->loop->timer($interval, $interval, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if (!isset($this->timers[$timer])) { + return; + } + + $event = $this->timers[$timer]; + $event->stop(); + $this->timers->detach($timer); + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + $wasJustStopped = !$this->running; + $nothingLeftToDo = !$this->readStreams + && !$this->writeStreams + && !$this->timers->count() + && $this->signals->isEmpty(); + + $flags = Ev::RUN_ONCE; + if ($wasJustStopped || $hasPendingCallbacks) { + $flags |= Ev::RUN_NOWAIT; + } elseif ($nothingLeftToDo) { + break; + } + + $this->loop->run($flags); + } + } + + public function stop() + { + $this->running = false; + } + + public function __destruct() + { + /** @var TimerInterface $timer */ + foreach ($this->timers as $timer) { + $this->cancelTimer($timer); + } + + foreach ($this->readStreams as $key => $stream) { + $this->removeReadStream($key); + } + + foreach ($this->writeStreams as $key => $stream) { + $this->removeWriteStream($key); + } + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) { + $this->signals->call($signal); + }); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal]->stop(); + unset($this->signalEvents[$signal]); + } + } +} diff --git a/assets/php/vendor/react/event-loop/src/ExtEventLoop.php b/assets/php/vendor/react/event-loop/src/ExtEventLoop.php new file mode 100644 index 0000000..622dd47 --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/ExtEventLoop.php @@ -0,0 +1,259 @@ +requireFeatures(EventBaseConfig::FEATURE_FDS); + + $this->eventBase = new EventBase($config); + $this->futureTickQueue = new FutureTickQueue(); + $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + + $this->createTimerCallback(); + $this->createStreamCallback(); + } + + public function addReadStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->readListeners[$key])) { + return; + } + + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback); + $event->add(); + $this->readEvents[$key] = $event; + $this->readListeners[$key] = $listener; + + // ext-event does not increase refcount on stream resources for PHP 7+ + // manually keep track of stream resource to prevent premature garbage collection + if (PHP_VERSION_ID >= 70000) { + $this->readRefs[$key] = $stream; + } + } + + public function addWriteStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->writeListeners[$key])) { + return; + } + + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback); + $event->add(); + $this->writeEvents[$key] = $event; + $this->writeListeners[$key] = $listener; + + // ext-event does not increase refcount on stream resources for PHP 7+ + // manually keep track of stream resource to prevent premature garbage collection + if (PHP_VERSION_ID >= 70000) { + $this->writeRefs[$key] = $stream; + } + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + if (isset($this->readEvents[$key])) { + $this->readEvents[$key]->free(); + unset( + $this->readEvents[$key], + $this->readListeners[$key], + $this->readRefs[$key] + ); + } + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + if (isset($this->writeEvents[$key])) { + $this->writeEvents[$key]->free(); + unset( + $this->writeEvents[$key], + $this->writeListeners[$key], + $this->writeRefs[$key] + ); + } + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if ($this->timerEvents->contains($timer)) { + $this->timerEvents[$timer]->free(); + $this->timerEvents->detach($timer); + } + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call')); + $this->signalEvents[$signal]->add(); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + $this->signalEvents[$signal]->free(); + unset($this->signalEvents[$signal]); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $flags = EventBase::LOOP_ONCE; + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $flags |= EventBase::LOOP_NONBLOCK; + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { + break; + } + + $this->eventBase->loop($flags); + } + } + + public function stop() + { + $this->running = false; + } + + /** + * Schedule a timer for execution. + * + * @param TimerInterface $timer + */ + private function scheduleTimer(TimerInterface $timer) + { + $flags = Event::TIMEOUT; + + if ($timer->isPeriodic()) { + $flags |= Event::PERSIST; + } + + $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer); + $this->timerEvents[$timer] = $event; + + $event->add($timer->getInterval()); + } + + /** + * Create a callback used as the target of timer events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createTimerCallback() + { + $timers = $this->timerEvents; + $this->timerCallback = function ($_, $__, $timer) use ($timers) { + call_user_func($timer->getCallback(), $timer); + + if (!$timer->isPeriodic() && $timers->contains($timer)) { + $this->cancelTimer($timer); + } + }; + } + + /** + * Create a callback used as the target of stream events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createStreamCallback() + { + $read =& $this->readListeners; + $write =& $this->writeListeners; + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { + $key = (int) $stream; + + if (Event::READ === (Event::READ & $flags) && isset($read[$key])) { + call_user_func($read[$key], $stream); + } + + if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) { + call_user_func($write[$key], $stream); + } + }; + } +} diff --git a/assets/php/vendor/react/event-loop/src/ExtLibevLoop.php b/assets/php/vendor/react/event-loop/src/ExtLibevLoop.php new file mode 100644 index 0000000..d3b0df8 --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/ExtLibevLoop.php @@ -0,0 +1,199 @@ +loop = new EventLoop(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + } + + public function addReadStream($stream, $listener) + { + if (isset($this->readEvents[(int) $stream])) { + return; + } + + $callback = function () use ($stream, $listener) { + call_user_func($listener, $stream); + }; + + $event = new IOEvent($callback, $stream, IOEvent::READ); + $this->loop->add($event); + + $this->readEvents[(int) $stream] = $event; + } + + public function addWriteStream($stream, $listener) + { + if (isset($this->writeEvents[(int) $stream])) { + return; + } + + $callback = function () use ($stream, $listener) { + call_user_func($listener, $stream); + }; + + $event = new IOEvent($callback, $stream, IOEvent::WRITE); + $this->loop->add($event); + + $this->writeEvents[(int) $stream] = $event; + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + if (isset($this->readEvents[$key])) { + $this->readEvents[$key]->stop(); + $this->loop->remove($this->readEvents[$key]); + unset($this->readEvents[$key]); + } + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + if (isset($this->writeEvents[$key])) { + $this->writeEvents[$key]->stop(); + $this->loop->remove($this->writeEvents[$key]); + unset($this->writeEvents[$key]); + } + } + + public function addTimer($interval, $callback) + { + $timer = new Timer( $interval, $callback, false); + + $that = $this; + $timers = $this->timerEvents; + $callback = function () use ($timer, $timers, $that) { + call_user_func($timer->getCallback(), $timer); + + if ($timers->contains($timer)) { + $that->cancelTimer($timer); + } + }; + + $event = new TimerEvent($callback, $timer->getInterval()); + $this->timerEvents->attach($timer, $event); + $this->loop->add($event); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + }; + + $event = new TimerEvent($callback, $interval, $interval); + $this->timerEvents->attach($timer, $event); + $this->loop->add($event); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if (isset($this->timerEvents[$timer])) { + $this->loop->remove($this->timerEvents[$timer]); + $this->timerEvents->detach($timer); + } + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $signals = $this->signals; + $this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) { + $signals->call($signal); + }, $signal); + $this->loop->add($this->signalEvents[$signal]); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + $this->signalEvents[$signal]->stop(); + $this->loop->remove($this->signalEvents[$signal]); + unset($this->signalEvents[$signal]); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $flags = EventLoop::RUN_ONCE; + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $flags |= EventLoop::RUN_NOWAIT; + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { + break; + } + + $this->loop->run($flags); + } + } + + public function stop() + { + $this->running = false; + } +} diff --git a/assets/php/vendor/react/event-loop/src/ExtLibeventLoop.php b/assets/php/vendor/react/event-loop/src/ExtLibeventLoop.php new file mode 100644 index 0000000..427f8db --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/ExtLibeventLoop.php @@ -0,0 +1,283 @@ +eventBase = event_base_new(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + + $this->createTimerCallback(); + $this->createStreamCallback(); + } + + public function addReadStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->readListeners[$key])) { + return; + } + + $event = event_new(); + event_set($event, $stream, EV_PERSIST | EV_READ, $this->streamCallback); + event_base_set($event, $this->eventBase); + event_add($event); + + $this->readEvents[$key] = $event; + $this->readListeners[$key] = $listener; + } + + public function addWriteStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->writeListeners[$key])) { + return; + } + + $event = event_new(); + event_set($event, $stream, EV_PERSIST | EV_WRITE, $this->streamCallback); + event_base_set($event, $this->eventBase); + event_add($event); + + $this->writeEvents[$key] = $event; + $this->writeListeners[$key] = $listener; + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + if (isset($this->readListeners[$key])) { + $event = $this->readEvents[$key]; + event_del($event); + event_free($event); + + unset( + $this->readEvents[$key], + $this->readListeners[$key] + ); + } + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + if (isset($this->writeListeners[$key])) { + $event = $this->writeEvents[$key]; + event_del($event); + event_free($event); + + unset( + $this->writeEvents[$key], + $this->writeListeners[$key] + ); + } + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if ($this->timerEvents->contains($timer)) { + $event = $this->timerEvents[$timer]; + event_del($event); + event_free($event); + + $this->timerEvents->detach($timer); + } + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = event_new(); + event_set($this->signalEvents[$signal], $signal, EV_PERSIST | EV_SIGNAL, array($this->signals, 'call')); + event_base_set($this->signalEvents[$signal], $this->eventBase); + event_add($this->signalEvents[$signal]); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + event_del($this->signalEvents[$signal]); + event_free($this->signalEvents[$signal]); + unset($this->signalEvents[$signal]); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $flags = EVLOOP_ONCE; + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $flags |= EVLOOP_NONBLOCK; + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { + break; + } + + event_base_loop($this->eventBase, $flags); + } + } + + public function stop() + { + $this->running = false; + } + + /** + * Schedule a timer for execution. + * + * @param TimerInterface $timer + */ + private function scheduleTimer(TimerInterface $timer) + { + $this->timerEvents[$timer] = $event = event_timer_new(); + + event_timer_set($event, $this->timerCallback, $timer); + event_base_set($event, $this->eventBase); + event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND); + } + + /** + * Create a callback used as the target of timer events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createTimerCallback() + { + $that = $this; + $timers = $this->timerEvents; + $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) { + call_user_func($timer->getCallback(), $timer); + + // Timer already cancelled ... + if (!$timers->contains($timer)) { + return; + } + + // Reschedule periodic timers ... + if ($timer->isPeriodic()) { + event_add( + $timers[$timer], + $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND + ); + + // Clean-up one shot timers ... + } else { + $that->cancelTimer($timer); + } + }; + } + + /** + * Create a callback used as the target of stream events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createStreamCallback() + { + $read =& $this->readListeners; + $write =& $this->writeListeners; + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { + $key = (int) $stream; + + if (EV_READ === (EV_READ & $flags) && isset($read[$key])) { + call_user_func($read[$key], $stream); + } + + if (EV_WRITE === (EV_WRITE & $flags) && isset($write[$key])) { + call_user_func($write[$key], $stream); + } + }; + } +} diff --git a/assets/php/vendor/react/event-loop/src/Factory.php b/assets/php/vendor/react/event-loop/src/Factory.php new file mode 100644 index 0000000..b46fc07 --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/Factory.php @@ -0,0 +1,41 @@ +addReadStream($stream, function ($stream) use ($name) { + * echo $name . ' said: ' . fread($stream); + * }); + * ``` + * + * See also [example #11](examples). + * + * You can invoke [`removeReadStream()`](#removereadstream) to remove the + * read event listener for this stream. + * + * The execution order of listeners when multiple streams become ready at + * the same time is not guaranteed. + * + * @param resource $stream The PHP stream resource to check. + * @param callable $listener Invoked when the stream is ready. + * @throws \Exception if the given resource type is not supported by this loop implementation + * @see self::removeReadStream() + */ + public function addReadStream($stream, $listener); + + /** + * [Advanced] Register a listener to be notified when a stream is ready to write. + * + * Note that this low-level API is considered advanced usage. + * Most use cases should probably use the higher-level + * [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) + * instead. + * + * The first parameter MUST be a valid stream resource that supports + * checking whether it is ready to write by this loop implementation. + * A single stream resource MUST NOT be added more than once. + * Instead, either call [`removeWriteStream()`](#removewritestream) first or + * react to this event with a single listener and then dispatch from this + * listener. This method MAY throw an `Exception` if the given resource type + * is not supported by this loop implementation. + * + * The listener callback function MUST be able to accept a single parameter, + * the stream resource added by this method or you MAY use a function which + * has no parameters at all. + * + * The listener callback function MUST NOT throw an `Exception`. + * The return value of the listener callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * $loop->addWriteStream($stream, function ($stream) use ($name) { + * fwrite($stream, 'Hello ' . $name); + * }); + * ``` + * + * See also [example #12](examples). + * + * You can invoke [`removeWriteStream()`](#removewritestream) to remove the + * write event listener for this stream. + * + * The execution order of listeners when multiple streams become ready at + * the same time is not guaranteed. + * + * Some event loop implementations are known to only trigger the listener if + * the stream *becomes* readable (edge-triggered) and may not trigger if the + * stream has already been readable from the beginning. + * This also implies that a stream may not be recognized as readable when data + * is still left in PHP's internal stream buffers. + * As such, it's recommended to use `stream_set_read_buffer($stream, 0);` + * to disable PHP's internal read buffer in this case. + * + * @param resource $stream The PHP stream resource to check. + * @param callable $listener Invoked when the stream is ready. + * @throws \Exception if the given resource type is not supported by this loop implementation + * @see self::removeWriteStream() + */ + public function addWriteStream($stream, $listener); + + /** + * Remove the read event listener for the given stream. + * + * Removing a stream from the loop that has already been removed or trying + * to remove a stream that was never added or is invalid has no effect. + * + * @param resource $stream The PHP stream resource. + */ + public function removeReadStream($stream); + + /** + * Remove the write event listener for the given stream. + * + * Removing a stream from the loop that has already been removed or trying + * to remove a stream that was never added or is invalid has no effect. + * + * @param resource $stream The PHP stream resource. + */ + public function removeWriteStream($stream); + + /** + * Enqueue a callback to be invoked once after the given interval. + * + * The timer callback function MUST be able to accept a single parameter, + * the timer instance as also returned by this method or you MAY use a + * function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure + * the callback will be invoked only once after the given interval. + * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. + * + * ```php + * $loop->addTimer(0.8, function () { + * echo 'world!' . PHP_EOL; + * }); + * + * $loop->addTimer(0.3, function () { + * echo 'hello '; + * }); + * ``` + * + * See also [example #1](examples). + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * function hello($name, LoopInterface $loop) + * { + * $loop->addTimer(1.0, function () use ($name) { + * echo "hello $name\n"; + * }); + * } + * + * hello('Tester', $loop); + * ``` + * + * This interface does not enforce any particular timer resolution, so + * special care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Event loop implementations SHOULD work on + * a best effort basis and SHOULD provide at least millisecond accuracy + * unless otherwise noted. Many existing event loop implementations are + * known to provide microsecond accuracy, but it's generally not recommended + * to rely on this high precision. + * + * Similarly, the execution order of timers scheduled to execute at the + * same time (within its possible accuracy) is not guaranteed. + * + * This interface suggests that event loop implementations SHOULD use a + * monotonic time source if available. Given that a monotonic time source is + * not available on PHP by default, event loop implementations MAY fall back + * to using wall-clock time. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you schedule a timer to trigger in 30s and then adjust + * your system time forward by 20s, the timer SHOULD still trigger in 30s. + * See also [event loop implementations](#loop-implementations) for more details. + * + * @param int|float $interval The number of seconds to wait before execution. + * @param callable $callback The callback to invoke. + * + * @return TimerInterface + */ + public function addTimer($interval, $callback); + + /** + * Enqueue a callback to be invoked repeatedly after the given interval. + * + * The timer callback function MUST be able to accept a single parameter, + * the timer instance as also returned by this method or you MAY use a + * function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * Unlike [`addTimer()`](#addtimer), this method will ensure the the + * callback will be invoked infinitely after the given interval or until you + * invoke [`cancelTimer`](#canceltimer). + * + * ```php + * $timer = $loop->addPeriodicTimer(0.1, function () { + * echo 'tick!' . PHP_EOL; + * }); + * + * $loop->addTimer(1.0, function () use ($loop, $timer) { + * $loop->cancelTimer($timer); + * echo 'Done' . PHP_EOL; + * }); + * ``` + * + * See also [example #2](examples). + * + * If you want to limit the number of executions, you can bind + * arbitrary data to a callback closure like this: + * + * ```php + * function hello($name, LoopInterface $loop) + * { + * $n = 3; + * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + * if ($n > 0) { + * --$n; + * echo "hello $name\n"; + * } else { + * $loop->cancelTimer($timer); + * } + * }); + * } + * + * hello('Tester', $loop); + * ``` + * + * This interface does not enforce any particular timer resolution, so + * special care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Event loop implementations SHOULD work on + * a best effort basis and SHOULD provide at least millisecond accuracy + * unless otherwise noted. Many existing event loop implementations are + * known to provide microsecond accuracy, but it's generally not recommended + * to rely on this high precision. + * + * Similarly, the execution order of timers scheduled to execute at the + * same time (within its possible accuracy) is not guaranteed. + * + * This interface suggests that event loop implementations SHOULD use a + * monotonic time source if available. Given that a monotonic time source is + * not available on PHP by default, event loop implementations MAY fall back + * to using wall-clock time. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you schedule a timer to trigger in 30s and then adjust + * your system time forward by 20s, the timer SHOULD still trigger in 30s. + * See also [event loop implementations](#loop-implementations) for more details. + * + * Additionally, periodic timers may be subject to timer drift due to + * re-scheduling after each invocation. As such, it's generally not + * recommended to rely on this for high precision intervals with millisecond + * accuracy or below. + * + * @param int|float $interval The number of seconds to wait before execution. + * @param callable $callback The callback to invoke. + * + * @return TimerInterface + */ + public function addPeriodicTimer($interval, $callback); + + /** + * Cancel a pending timer. + * + * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + * + * Calling this method on a timer instance that has not been added to this + * loop instance or on a timer that has already been cancelled has no effect. + * + * @param TimerInterface $timer The timer to cancel. + * + * @return void + */ + public function cancelTimer(TimerInterface $timer); + + /** + * Schedule a callback to be invoked on a future tick of the event loop. + * + * This works very much similar to timers with an interval of zero seconds, + * but does not require the overhead of scheduling a timer queue. + * + * The tick callback function MUST be able to accept zero parameters. + * + * The tick callback function MUST NOT throw an `Exception`. + * The return value of the tick callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * function hello($name, LoopInterface $loop) + * { + * $loop->futureTick(function () use ($name) { + * echo "hello $name\n"; + * }); + * } + * + * hello('Tester', $loop); + * ``` + * + * Unlike timers, tick callbacks are guaranteed to be executed in the order + * they are enqueued. + * Also, once a callback is enqueued, there's no way to cancel this operation. + * + * This is often used to break down bigger tasks into smaller steps (a form + * of cooperative multitasking). + * + * ```php + * $loop->futureTick(function () { + * echo 'b'; + * }); + * $loop->futureTick(function () { + * echo 'c'; + * }); + * echo 'a'; + * ``` + * + * See also [example #3](examples). + * + * @param callable $listener The callback to invoke. + * + * @return void + */ + public function futureTick($listener); + + /** + * Register a listener to be notified when a signal has been caught by this process. + * + * This is useful to catch user interrupt signals or shutdown signals from + * tools like `supervisor` or `systemd`. + * + * The listener callback function MUST be able to accept a single parameter, + * the signal added by this method or you MAY use a function which + * has no parameters at all. + * + * The listener callback function MUST NOT throw an `Exception`. + * The return value of the listener callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * ```php + * $loop->addSignal(SIGINT, function (int $signal) { + * echo 'Caught user interrupt signal' . PHP_EOL; + * }); + * ``` + * + * See also [example #4](examples). + * + * Signaling is only available on Unix-like platform, Windows isn't + * supported due to operating system limitations. + * This method may throw a `BadMethodCallException` if signals aren't + * supported on this platform, for example when required extensions are + * missing. + * + * **Note: A listener can only be added once to the same signal, any + * attempts to add it more then once will be ignored.** + * + * @param int $signal + * @param callable $listener + * + * @throws \BadMethodCallException when signals aren't supported on this + * platform, for example when required extensions are missing. + * + * @return void + */ + public function addSignal($signal, $listener); + + /** + * Removes a previously added signal listener. + * + * ```php + * $loop->removeSignal(SIGINT, $listener); + * ``` + * + * Any attempts to remove listeners that aren't registered will be ignored. + * + * @param int $signal + * @param callable $listener + * + * @return void + */ + public function removeSignal($signal, $listener); + + /** + * Run the event loop until there are no more tasks to perform. + * + * For many applications, this method is the only directly visible + * invocation on the event loop. + * As a rule of thumb, it is usally recommended to attach everything to the + * same loop instance and then run the loop once at the bottom end of the + * application. + * + * ```php + * $loop->run(); + * ``` + * + * This method will keep the loop running until there are no more tasks + * to perform. In other words: This method will block until the last + * timer, stream and/or signal has been removed. + * + * Likewise, it is imperative to ensure the application actually invokes + * this method once. Adding listeners to the loop and missing to actually + * run it will result in the application exiting without actually waiting + * for any of the attached listeners. + * + * This method MUST NOT be called while the loop is already running. + * This method MAY be called more than once after it has explicity been + * [`stop()`ped](#stop) or after it automatically stopped because it + * previously did no longer have anything to do. + * + * @return void + */ + public function run(); + + /** + * Instruct a running event loop to stop. + * + * This method is considered advanced usage and should be used with care. + * As a rule of thumb, it is usually recommended to let the loop stop + * only automatically when it no longer has anything to do. + * + * This method can be used to explicitly instruct the event loop to stop: + * + * ```php + * $loop->addTimer(3.0, function () use ($loop) { + * $loop->stop(); + * }); + * ``` + * + * Calling this method on a loop instance that is not currently running or + * on a loop instance that has already been stopped has no effect. + * + * @return void + */ + public function stop(); +} diff --git a/assets/php/vendor/react/event-loop/src/SignalsHandler.php b/assets/php/vendor/react/event-loop/src/SignalsHandler.php new file mode 100644 index 0000000..523e1ca --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/SignalsHandler.php @@ -0,0 +1,63 @@ +signals[$signal])) { + $this->signals[$signal] = array(); + } + + if (in_array($listener, $this->signals[$signal])) { + return; + } + + $this->signals[$signal][] = $listener; + } + + public function remove($signal, $listener) + { + if (!isset($this->signals[$signal])) { + return; + } + + $index = \array_search($listener, $this->signals[$signal], true); + unset($this->signals[$signal][$index]); + + if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) { + unset($this->signals[$signal]); + } + } + + public function call($signal) + { + if (!isset($this->signals[$signal])) { + return; + } + + foreach ($this->signals[$signal] as $listener) { + \call_user_func($listener, $signal); + } + } + + public function count($signal) + { + if (!isset($this->signals[$signal])) { + return 0; + } + + return \count($this->signals[$signal]); + } + + public function isEmpty() + { + return !$this->signals; + } +} diff --git a/assets/php/vendor/react/event-loop/src/StreamSelectLoop.php b/assets/php/vendor/react/event-loop/src/StreamSelectLoop.php new file mode 100644 index 0000000..e82e9e4 --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/StreamSelectLoop.php @@ -0,0 +1,275 @@ +futureTickQueue = new FutureTickQueue(); + $this->timers = new Timers(); + $this->pcntl = extension_loaded('pcntl'); + $this->signals = new SignalsHandler(); + } + + public function addReadStream($stream, $listener) + { + $key = (int) $stream; + + if (!isset($this->readStreams[$key])) { + $this->readStreams[$key] = $stream; + $this->readListeners[$key] = $listener; + } + } + + public function addWriteStream($stream, $listener) + { + $key = (int) $stream; + + if (!isset($this->writeStreams[$key])) { + $this->writeStreams[$key] = $stream; + $this->writeListeners[$key] = $listener; + } + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + unset( + $this->readStreams[$key], + $this->readListeners[$key] + ); + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + unset( + $this->writeStreams[$key], + $this->writeListeners[$key] + ); + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $this->timers->add($timer); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $this->timers->add($timer); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + $this->timers->cancel($timer); + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + if ($this->pcntl === false) { + throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"'); + } + + $first = $this->signals->count($signal) === 0; + $this->signals->add($signal, $listener); + + if ($first) { + \pcntl_signal($signal, array($this->signals, 'call')); + } + } + + public function removeSignal($signal, $listener) + { + if (!$this->signals->count($signal)) { + return; + } + + $this->signals->remove($signal, $listener); + + if ($this->signals->count($signal) === 0) { + \pcntl_signal($signal, SIG_DFL); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $this->timers->tick(); + + // Future-tick queue has pending callbacks ... + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $timeout = 0; + + // There is a pending timer, only block until it is due ... + } elseif ($scheduledAt = $this->timers->getFirst()) { + $timeout = $scheduledAt - $this->timers->getTime(); + if ($timeout < 0) { + $timeout = 0; + } else { + // Convert float seconds to int microseconds. + // Ensure we do not exceed maximum integer size, which may + // cause the loop to tick once every ~35min on 32bit systems. + $timeout *= self::MICROSECONDS_PER_SECOND; + $timeout = $timeout > PHP_INT_MAX ? PHP_INT_MAX : (int)$timeout; + } + + // The only possible event is stream or signal activity, so wait forever ... + } elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) { + $timeout = null; + + // There's nothing left to do ... + } else { + break; + } + + $this->waitForStreamActivity($timeout); + } + } + + public function stop() + { + $this->running = false; + } + + /** + * Wait/check for stream activity, or until the next timer is due. + * + * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. + */ + private function waitForStreamActivity($timeout) + { + $read = $this->readStreams; + $write = $this->writeStreams; + + $available = $this->streamSelect($read, $write, $timeout); + if ($this->pcntl) { + \pcntl_signal_dispatch(); + } + if (false === $available) { + // if a system call has been interrupted, + // we cannot rely on it's outcome + return; + } + + foreach ($read as $stream) { + $key = (int) $stream; + + if (isset($this->readListeners[$key])) { + call_user_func($this->readListeners[$key], $stream); + } + } + + foreach ($write as $stream) { + $key = (int) $stream; + + if (isset($this->writeListeners[$key])) { + call_user_func($this->writeListeners[$key], $stream); + } + } + } + + /** + * Emulate a stream_select() implementation that does not break when passed + * empty stream arrays. + * + * @param array &$read An array of read streams to select upon. + * @param array &$write An array of write streams to select upon. + * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. + * + * @return integer|false The total number of streams that are ready for read/write. + * Can return false if stream_select() is interrupted by a signal. + */ + private function streamSelect(array &$read, array &$write, $timeout) + { + if ($read || $write) { + $except = null; + + // suppress warnings that occur, when stream_select is interrupted by a signal + return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + } + + $timeout && usleep($timeout); + + return 0; + } +} diff --git a/assets/php/vendor/react/event-loop/src/Tick/FutureTickQueue.php b/assets/php/vendor/react/event-loop/src/Tick/FutureTickQueue.php new file mode 100644 index 0000000..c79afc5 --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/Tick/FutureTickQueue.php @@ -0,0 +1,60 @@ +queue = new SplQueue(); + } + + /** + * Add a callback to be invoked on a future tick of the event loop. + * + * Callbacks are guaranteed to be executed in the order they are enqueued. + * + * @param callable $listener The callback to invoke. + */ + public function add($listener) + { + $this->queue->enqueue($listener); + } + + /** + * Flush the callback queue. + */ + public function tick() + { + // Only invoke as many callbacks as were on the queue when tick() was called. + $count = $this->queue->count(); + + while ($count--) { + call_user_func( + $this->queue->dequeue() + ); + } + } + + /** + * Check if the next tick queue is empty. + * + * @return boolean + */ + public function isEmpty() + { + return $this->queue->isEmpty(); + } +} diff --git a/assets/php/vendor/react/event-loop/src/Timer/Timer.php b/assets/php/vendor/react/event-loop/src/Timer/Timer.php new file mode 100644 index 0000000..da3602a --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/Timer/Timer.php @@ -0,0 +1,55 @@ +interval = (float) $interval; + $this->callback = $callback; + $this->periodic = (bool) $periodic; + } + + public function getInterval() + { + return $this->interval; + } + + public function getCallback() + { + return $this->callback; + } + + public function isPeriodic() + { + return $this->periodic; + } +} diff --git a/assets/php/vendor/react/event-loop/src/Timer/Timers.php b/assets/php/vendor/react/event-loop/src/Timer/Timers.php new file mode 100644 index 0000000..17bbdac --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/Timer/Timers.php @@ -0,0 +1,109 @@ +timers = new SplObjectStorage(); + $this->scheduler = new SplPriorityQueue(); + } + + public function updateTime() + { + return $this->time = microtime(true); + } + + public function getTime() + { + return $this->time ?: $this->updateTime(); + } + + public function add(TimerInterface $timer) + { + $interval = $timer->getInterval(); + $scheduledAt = $interval + microtime(true); + + $this->timers->attach($timer, $scheduledAt); + $this->scheduler->insert($timer, -$scheduledAt); + } + + public function contains(TimerInterface $timer) + { + return $this->timers->contains($timer); + } + + public function cancel(TimerInterface $timer) + { + $this->timers->detach($timer); + } + + public function getFirst() + { + while ($this->scheduler->count()) { + $timer = $this->scheduler->top(); + + if ($this->timers->contains($timer)) { + return $this->timers[$timer]; + } + + $this->scheduler->extract(); + } + + return null; + } + + public function isEmpty() + { + return count($this->timers) === 0; + } + + public function tick() + { + $time = $this->updateTime(); + $timers = $this->timers; + $scheduler = $this->scheduler; + + while (!$scheduler->isEmpty()) { + $timer = $scheduler->top(); + + if (!isset($timers[$timer])) { + $scheduler->extract(); + $timers->detach($timer); + + continue; + } + + if ($timers[$timer] >= $time) { + break; + } + + $scheduler->extract(); + call_user_func($timer->getCallback(), $timer); + + if ($timer->isPeriodic() && isset($timers[$timer])) { + $timers[$timer] = $scheduledAt = $timer->getInterval() + $time; + $scheduler->insert($timer, -$scheduledAt); + } else { + $timers->detach($timer); + } + } + } +} diff --git a/assets/php/vendor/react/event-loop/src/TimerInterface.php b/assets/php/vendor/react/event-loop/src/TimerInterface.php new file mode 100644 index 0000000..cdcf773 --- /dev/null +++ b/assets/php/vendor/react/event-loop/src/TimerInterface.php @@ -0,0 +1,27 @@ +tickTimeout = 0.02; + $this->loop = $this->createLoop(); + } + + abstract public function createLoop(); + + public function createSocketPair() + { + $domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX; + $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + + foreach ($sockets as $socket) { + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($socket, 0); + } + } + + return $sockets; + } + + public function testAddReadStream() + { + list ($input, $output) = $this->createSocketPair(); + + $this->loop->addReadStream($input, $this->expectCallableExactly(2)); + + fwrite($output, "foo\n"); + $this->tickLoop($this->loop); + + fwrite($output, "bar\n"); + $this->tickLoop($this->loop); + } + + public function testAddReadStreamIgnoresSecondCallable() + { + list ($input, $output) = $this->createSocketPair(); + + $this->loop->addReadStream($input, $this->expectCallableExactly(2)); + $this->loop->addReadStream($input, $this->expectCallableNever()); + + fwrite($output, "foo\n"); + $this->tickLoop($this->loop); + + fwrite($output, "bar\n"); + $this->tickLoop($this->loop); + } + + public function testAddReadStreamReceivesDataFromStreamReference() + { + $this->received = ''; + $this->subAddReadStreamReceivesDataFromStreamReference(); + $this->assertEquals('', $this->received); + + $this->assertRunFasterThan($this->tickTimeout * 2); + $this->assertEquals('[hello]X', $this->received); + } + + /** + * Helper for above test. This happens in another helper method to verify + * the loop keeps track of assigned stream resources (refcount). + */ + private function subAddReadStreamReceivesDataFromStreamReference() + { + list ($input, $output) = $this->createSocketPair(); + + fwrite($input, 'hello'); + fclose($input); + + $loop = $this->loop; + $received =& $this->received; + $loop->addReadStream($output, function ($output) use ($loop, &$received) { + $chunk = fread($output, 1024); + if ($chunk === '') { + $received .= 'X'; + $loop->removeReadStream($output); + fclose($output); + } else { + $received .= '[' . $chunk . ']'; + } + }); + } + + public function testAddWriteStream() + { + list ($input) = $this->createSocketPair(); + + $this->loop->addWriteStream($input, $this->expectCallableExactly(2)); + $this->tickLoop($this->loop); + $this->tickLoop($this->loop); + } + + public function testAddWriteStreamIgnoresSecondCallable() + { + list ($input) = $this->createSocketPair(); + + $this->loop->addWriteStream($input, $this->expectCallableExactly(2)); + $this->loop->addWriteStream($input, $this->expectCallableNever()); + $this->tickLoop($this->loop); + $this->tickLoop($this->loop); + } + + public function testRemoveReadStreamInstantly() + { + list ($input, $output) = $this->createSocketPair(); + + $this->loop->addReadStream($input, $this->expectCallableNever()); + $this->loop->removeReadStream($input); + + fwrite($output, "bar\n"); + $this->tickLoop($this->loop); + } + + public function testRemoveReadStreamAfterReading() + { + list ($input, $output) = $this->createSocketPair(); + + $this->loop->addReadStream($input, $this->expectCallableOnce()); + + fwrite($output, "foo\n"); + $this->tickLoop($this->loop); + + $this->loop->removeReadStream($input); + + fwrite($output, "bar\n"); + $this->tickLoop($this->loop); + } + + public function testRemoveWriteStreamInstantly() + { + list ($input) = $this->createSocketPair(); + + $this->loop->addWriteStream($input, $this->expectCallableNever()); + $this->loop->removeWriteStream($input); + $this->tickLoop($this->loop); + } + + public function testRemoveWriteStreamAfterWriting() + { + list ($input) = $this->createSocketPair(); + + $this->loop->addWriteStream($input, $this->expectCallableOnce()); + $this->tickLoop($this->loop); + + $this->loop->removeWriteStream($input); + $this->tickLoop($this->loop); + } + + public function testRemoveStreamForReadOnly() + { + list ($input, $output) = $this->createSocketPair(); + + $this->loop->addReadStream($input, $this->expectCallableNever()); + $this->loop->addWriteStream($output, $this->expectCallableOnce()); + $this->loop->removeReadStream($input); + + fwrite($output, "foo\n"); + $this->tickLoop($this->loop); + } + + public function testRemoveStreamForWriteOnly() + { + list ($input, $output) = $this->createSocketPair(); + + fwrite($output, "foo\n"); + + $this->loop->addReadStream($input, $this->expectCallableOnce()); + $this->loop->addWriteStream($output, $this->expectCallableNever()); + $this->loop->removeWriteStream($output); + + $this->tickLoop($this->loop); + } + + public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesEndsLoop() + { + list($stream, $other) = $this->createSocketPair(); + stream_set_blocking($stream, false); + stream_set_blocking($other, false); + + // dummy writable handler + $this->loop->addWriteStream($stream, function () { }); + + // remove stream when the stream is readable (closes) + $loop = $this->loop; + $loop->addReadStream($stream, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); + fclose($stream); + }); + + // close other side + fclose($other); + + $this->assertRunFasterThan($this->tickTimeout); + } + + public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesOnEndOfFileEndsLoop() + { + list($stream, $other) = $this->createSocketPair(); + stream_set_blocking($stream, false); + stream_set_blocking($other, false); + + // dummy writable handler + $this->loop->addWriteStream($stream, function () { }); + + // remove stream when the stream is readable (closes) + $loop = $this->loop; + $loop->addReadStream($stream, function ($stream) use ($loop) { + $data = fread($stream, 1024); + if ($data !== '') { + return; + } + + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); + fclose($stream); + }); + + // send data and close stream + fwrite($other, str_repeat('.', static::PHP_DEFAULT_CHUNK_SIZE)); + $this->loop->addTimer(0.01, function () use ($other) { + fclose($other); + }); + + $this->assertRunFasterThan(0.1); + } + + public function testRemoveReadAndWriteStreamFromLoopWithClosingResourceEndsLoop() + { + // get only one part of the pair to ensure the other side will close immediately + list($stream) = $this->createSocketPair(); + stream_set_blocking($stream, false); + + // dummy writable handler + $this->loop->addWriteStream($stream, function () { }); + + // remove stream when the stream is readable (closes) + $loop = $this->loop; + $loop->addReadStream($stream, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); + fclose($stream); + }); + + $this->assertRunFasterThan($this->tickTimeout); + } + + public function testRemoveInvalid() + { + list ($stream) = $this->createSocketPair(); + + // remove a valid stream from the event loop that was never added in the first place + $this->loop->removeReadStream($stream); + $this->loop->removeWriteStream($stream); + + $this->assertTrue(true); + } + + /** @test */ + public function emptyRunShouldSimplyReturn() + { + $this->assertRunFasterThan($this->tickTimeout); + } + + /** @test */ + public function runShouldReturnWhenNoMoreFds() + { + list ($input, $output) = $this->createSocketPair(); + + $loop = $this->loop; + $this->loop->addReadStream($input, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + }); + + fwrite($output, "foo\n"); + + $this->assertRunFasterThan($this->tickTimeout * 2); + } + + /** @test */ + public function stopShouldStopRunningLoop() + { + list ($input, $output) = $this->createSocketPair(); + + $loop = $this->loop; + $this->loop->addReadStream($input, function ($stream) use ($loop) { + $loop->stop(); + }); + + fwrite($output, "foo\n"); + + $this->assertRunFasterThan($this->tickTimeout * 2); + } + + public function testStopShouldPreventRunFromBlocking() + { + $that = $this; + $this->loop->addTimer( + 1, + function () use ($that) { + $that->fail('Timer was executed.'); + } + ); + + $loop = $this->loop; + $this->loop->futureTick( + function () use ($loop) { + $loop->stop(); + } + ); + + $this->assertRunFasterThan($this->tickTimeout * 2); + } + + public function testIgnoreRemovedCallback() + { + // two independent streams, both should be readable right away + list ($input1, $output1) = $this->createSocketPair(); + list ($input2, $output2) = $this->createSocketPair(); + + $called = false; + + $loop = $this->loop; + $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) { + // stream1 is readable, remove stream2 as well => this will invalidate its callback + $loop->removeReadStream($stream); + $loop->removeReadStream($input2); + + $called = true; + }); + + // this callback would have to be called as well, but the first stream already removed us + $that = $this; + $loop->addReadStream($input2, function () use (& $called, $that) { + if ($called) { + $that->fail('Callback 2 must not be called after callback 1 was called'); + } + }); + + fwrite($output1, "foo\n"); + fwrite($output2, "foo\n"); + + $loop->run(); + + $this->assertTrue($called); + } + + public function testFutureTickEventGeneratedByFutureTick() + { + $loop = $this->loop; + $this->loop->futureTick( + function () use ($loop) { + $loop->futureTick( + function () { + echo 'future-tick' . PHP_EOL; + } + ); + } + ); + + $this->expectOutputString('future-tick' . PHP_EOL); + + $this->loop->run(); + } + + public function testFutureTick() + { + $called = false; + + $callback = function () use (&$called) { + $called = true; + }; + + $this->loop->futureTick($callback); + + $this->assertFalse($called); + + $this->tickLoop($this->loop); + + $this->assertTrue($called); + } + + public function testFutureTickFiresBeforeIO() + { + list ($stream) = $this->createSocketPair(); + + $this->loop->addWriteStream( + $stream, + function () { + echo 'stream' . PHP_EOL; + } + ); + + $this->loop->futureTick( + function () { + echo 'future-tick' . PHP_EOL; + } + ); + + $this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL); + + $this->tickLoop($this->loop); + } + + public function testRecursiveFutureTick() + { + list ($stream) = $this->createSocketPair(); + + $loop = $this->loop; + $this->loop->addWriteStream( + $stream, + function () use ($stream, $loop) { + echo 'stream' . PHP_EOL; + $loop->removeWriteStream($stream); + } + ); + + $this->loop->futureTick( + function () use ($loop) { + echo 'future-tick-1' . PHP_EOL; + $loop->futureTick( + function () { + echo 'future-tick-2' . PHP_EOL; + } + ); + } + ); + + $this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL); + + $this->loop->run(); + } + + public function testRunWaitsForFutureTickEvents() + { + list ($stream) = $this->createSocketPair(); + + $loop = $this->loop; + $this->loop->addWriteStream( + $stream, + function () use ($stream, $loop) { + $loop->removeWriteStream($stream); + $loop->futureTick( + function () { + echo 'future-tick' . PHP_EOL; + } + ); + } + ); + + $this->expectOutputString('future-tick' . PHP_EOL); + + $this->loop->run(); + } + + public function testFutureTickEventGeneratedByTimer() + { + $loop = $this->loop; + $this->loop->addTimer( + 0.001, + function () use ($loop) { + $loop->futureTick( + function () { + echo 'future-tick' . PHP_EOL; + } + ); + } + ); + + $this->expectOutputString('future-tick' . PHP_EOL); + + $this->loop->run(); + } + + public function testRemoveSignalNotRegisteredIsNoOp() + { + $this->loop->removeSignal(SIGINT, function () { }); + $this->assertTrue(true); + } + + public function testSignal() + { + if (!function_exists('posix_kill') || !function_exists('posix_getpid')) { + $this->markTestSkipped('Signal test skipped because functions "posix_kill" and "posix_getpid" are missing.'); + } + + $called = false; + $calledShouldNot = true; + + $timer = $this->loop->addPeriodicTimer(1, function () {}); + + $this->loop->addSignal(SIGUSR2, $func2 = function () use (&$calledShouldNot) { + $calledShouldNot = false; + }); + + $loop = $this->loop; + $this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer, $loop) { + $called = true; + $loop->removeSignal(SIGUSR1, $func1); + $loop->removeSignal(SIGUSR2, $func2); + $loop->cancelTimer($timer); + }); + + $this->loop->futureTick(function () { + posix_kill(posix_getpid(), SIGUSR1); + }); + + $this->loop->run(); + + $this->assertTrue($called); + $this->assertTrue($calledShouldNot); + } + + public function testSignalMultipleUsagesForTheSameListener() + { + $funcCallCount = 0; + $func = function () use (&$funcCallCount) { + $funcCallCount++; + }; + $this->loop->addTimer(1, function () {}); + + $this->loop->addSignal(SIGUSR1, $func); + $this->loop->addSignal(SIGUSR1, $func); + + $this->loop->addTimer(0.4, function () { + posix_kill(posix_getpid(), SIGUSR1); + }); + $loop = $this->loop; + $this->loop->addTimer(0.9, function () use (&$func, $loop) { + $loop->removeSignal(SIGUSR1, $func); + }); + + $this->loop->run(); + + $this->assertSame(1, $funcCallCount); + } + + public function testSignalsKeepTheLoopRunning() + { + $loop = $this->loop; + $function = function () {}; + $this->loop->addSignal(SIGUSR1, $function); + $this->loop->addTimer(1.5, function () use ($function, $loop) { + $loop->removeSignal(SIGUSR1, $function); + $loop->stop(); + }); + + $this->assertRunSlowerThan(1.5); + } + + public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() + { + $loop = $this->loop; + $function = function () {}; + $this->loop->addSignal(SIGUSR1, $function); + $this->loop->addTimer(1.5, function () use ($function, $loop) { + $loop->removeSignal(SIGUSR1, $function); + }); + + $this->assertRunFasterThan(1.6); + } + + public function testTimerIntervalCanBeFarInFuture() + { + // get only one part of the pair to ensure the other side will close immediately + list($stream) = $this->createSocketPair(); + + // start a timer very far in the future + $timer = $this->loop->addTimer(PHP_INT_MAX, function () { }); + + // remove stream and timer when the stream is readable (closes) + $loop = $this->loop; + $this->loop->addReadStream($stream, function ($stream) use ($timer, $loop) { + $loop->removeReadStream($stream); + $loop->cancelTimer($timer); + }); + + $this->assertRunFasterThan($this->tickTimeout); + } + + private function assertRunSlowerThan($minInterval) + { + $start = microtime(true); + + $this->loop->run(); + + $end = microtime(true); + $interval = $end - $start; + + $this->assertLessThan($interval, $minInterval); + } + + private function assertRunFasterThan($maxInterval) + { + $start = microtime(true); + + $this->loop->run(); + + $end = microtime(true); + $interval = $end - $start; + + $this->assertLessThan($maxInterval, $interval); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/CallableStub.php b/assets/php/vendor/react/event-loop/tests/CallableStub.php new file mode 100644 index 0000000..913d403 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/CallableStub.php @@ -0,0 +1,10 @@ +markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.'); + } + + return new ExtEvLoop(); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/ExtEventLoopTest.php b/assets/php/vendor/react/event-loop/tests/ExtEventLoopTest.php new file mode 100644 index 0000000..2f88d18 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/ExtEventLoopTest.php @@ -0,0 +1,84 @@ +markTestSkipped('libevent tests skipped on linux due to linux epoll issues.'); + } + + if (!extension_loaded('event')) { + $this->markTestSkipped('ext-event tests skipped because ext-event is not installed.'); + } + + return new ExtEventLoop(); + } + + public function createStream() + { + // Use a FIFO on linux to get around lack of support for disk-based file + // descriptors when using the EPOLL back-end. + if ('Linux' === PHP_OS) { + $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-'); + + unlink($this->fifoPath); + + posix_mkfifo($this->fifoPath, 0600); + + $stream = fopen($this->fifoPath, 'r+'); + + // ext-event (as of 1.8.1) does not yet support in-memory temporary + // streams. Setting maxmemory:0 and performing a write forces PHP to + // back this temporary stream with a real file. + // + // This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3 + // but remains unresolved (despite that issue being closed). + } else { + $stream = fopen('php://temp/maxmemory:0', 'r+'); + + fwrite($stream, 'x'); + ftruncate($stream, 0); + } + + return $stream; + } + + public function writeToStream($stream, $content) + { + if ('Linux' !== PHP_OS) { + return parent::writeToStream($stream, $content); + } + + fwrite($stream, $content); + } + + /** + * @group epoll-readable-error + */ + public function testCanUseReadableStreamWithFeatureFds() + { + if (PHP_VERSION_ID > 70000) { + $this->markTestSkipped('Memory stream not supported'); + } + + $this->loop = $this->createLoop(true); + + $input = fopen('php://temp/maxmemory:0', 'r+'); + + fwrite($input, 'x'); + ftruncate($input, 0); + + $this->loop->addReadStream($input, $this->expectCallableExactly(2)); + + fwrite($input, "foo\n"); + $this->tickLoop($this->loop); + + fwrite($input, "bar\n"); + $this->tickLoop($this->loop); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/ExtLibevLoopTest.php b/assets/php/vendor/react/event-loop/tests/ExtLibevLoopTest.php new file mode 100644 index 0000000..19a5e87 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/ExtLibevLoopTest.php @@ -0,0 +1,22 @@ +markTestSkipped('libev tests skipped because ext-libev is not installed.'); + } + + return new ExtLibevLoop(); + } + + public function testLibEvConstructor() + { + $loop = new ExtLibevLoop(); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/ExtLibeventLoopTest.php b/assets/php/vendor/react/event-loop/tests/ExtLibeventLoopTest.php new file mode 100644 index 0000000..8497065 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/ExtLibeventLoopTest.php @@ -0,0 +1,58 @@ +markTestSkipped('libevent tests skipped on linux due to linux epoll issues.'); + } + + if (!function_exists('event_base_new')) { + $this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.'); + } + + return new ExtLibeventLoop(); + } + + public function tearDown() + { + if (file_exists($this->fifoPath)) { + unlink($this->fifoPath); + } + } + + public function createStream() + { + if ('Linux' !== PHP_OS) { + return parent::createStream(); + } + + $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-'); + + unlink($this->fifoPath); + + // Use a FIFO on linux to get around lack of support for disk-based file + // descriptors when using the EPOLL back-end. + posix_mkfifo($this->fifoPath, 0600); + + $stream = fopen($this->fifoPath, 'r+'); + + return $stream; + } + + public function writeToStream($stream, $content) + { + if ('Linux' !== PHP_OS) { + return parent::writeToStream($stream, $content); + } + + fwrite($stream, $content); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/SignalsHandlerTest.php b/assets/php/vendor/react/event-loop/tests/SignalsHandlerTest.php new file mode 100644 index 0000000..f8b7df3 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/SignalsHandlerTest.php @@ -0,0 +1,55 @@ +assertSame(0, $callCount); + + $signals->add(SIGUSR1, $func); + $this->assertSame(0, $callCount); + + $signals->add(SIGUSR1, $func); + $this->assertSame(0, $callCount); + + $signals->add(SIGUSR1, $func); + $this->assertSame(0, $callCount); + + $signals->call(SIGUSR1); + $this->assertSame(1, $callCount); + + $signals->add(SIGUSR2, $func); + $this->assertSame(1, $callCount); + + $signals->add(SIGUSR2, $func); + $this->assertSame(1, $callCount); + + $signals->call(SIGUSR2); + $this->assertSame(2, $callCount); + + $signals->remove(SIGUSR2, $func); + $this->assertSame(2, $callCount); + + $signals->remove(SIGUSR2, $func); + $this->assertSame(2, $callCount); + + $signals->call(SIGUSR2); + $this->assertSame(2, $callCount); + + $signals->remove(SIGUSR1, $func); + $this->assertSame(2, $callCount); + + $signals->call(SIGUSR1); + $this->assertSame(2, $callCount); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/StreamSelectLoopTest.php b/assets/php/vendor/react/event-loop/tests/StreamSelectLoopTest.php new file mode 100644 index 0000000..bd19e1c --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/StreamSelectLoopTest.php @@ -0,0 +1,148 @@ +getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) { + $this->resetSignalHandlers(); + } + } + + public function createLoop() + { + return new StreamSelectLoop(); + } + + public function testStreamSelectTimeoutEmulation() + { + $this->loop->addTimer( + 0.05, + $this->expectCallableOnce() + ); + + $start = microtime(true); + + $this->loop->run(); + + $end = microtime(true); + $interval = $end - $start; + + $this->assertGreaterThan(0.04, $interval); + } + + public function signalProvider() + { + return array( + array('SIGUSR1'), + array('SIGHUP'), + array('SIGTERM'), + ); + } + + /** + * Test signal interrupt when no stream is attached to the loop + * @dataProvider signalProvider + */ + public function testSignalInterruptNoStream($signal) + { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('"pcntl" extension is required to run this test.'); + } + + // dispatch signal handler every 10ms for 0.1s + $check = $this->loop->addPeriodicTimer(0.01, function() { + pcntl_signal_dispatch(); + }); + $loop = $this->loop; + $loop->addTimer(0.1, function () use ($check, $loop) { + $loop->cancelTimer($check); + }); + + $handled = false; + $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) { + $handled = true; + })); + + // spawn external process to send signal to current process id + $this->forkSendSignal($signal); + + $this->loop->run(); + $this->assertTrue($handled); + } + + /** + * Test signal interrupt when a stream is attached to the loop + * @dataProvider signalProvider + */ + public function testSignalInterruptWithStream($signal) + { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('"pcntl" extension is required to run this test.'); + } + + // dispatch signal handler every 10ms + $this->loop->addPeriodicTimer(0.01, function() { + pcntl_signal_dispatch(); + }); + + // add stream to the loop + $loop = $this->loop; + list($writeStream, $readStream) = $this->createSocketPair(); + $loop->addReadStream($readStream, function ($stream) use ($loop) { + /** @var $loop LoopInterface */ + $read = fgets($stream); + if ($read === "end loop\n") { + $loop->stop(); + } + }); + $this->loop->addTimer(0.1, function() use ($writeStream) { + fwrite($writeStream, "end loop\n"); + }); + + $handled = false; + $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) { + $handled = true; + })); + + // spawn external process to send signal to current process id + $this->forkSendSignal($signal); + + $this->loop->run(); + + $this->assertTrue($handled); + } + + /** + * reset all signal handlers to default + */ + protected function resetSignalHandlers() + { + foreach($this->signalProvider() as $signal) { + pcntl_signal(constant($signal[0]), SIG_DFL); + } + } + + /** + * fork child process to send signal to current process id + */ + protected function forkSendSignal($signal) + { + $currentPid = posix_getpid(); + $childPid = pcntl_fork(); + if ($childPid == -1) { + $this->fail("Failed to fork child process!"); + } else if ($childPid === 0) { + // this is executed in the child process + usleep(20000); + posix_kill($currentPid, constant($signal)); + die(); + } + } +} diff --git a/assets/php/vendor/react/event-loop/tests/TestCase.php b/assets/php/vendor/react/event-loop/tests/TestCase.php new file mode 100644 index 0000000..dbdd54c --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/TestCase.php @@ -0,0 +1,53 @@ +createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock(); + } + + protected function tickLoop(LoopInterface $loop) + { + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + + $loop->run(); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php new file mode 100644 index 0000000..294e683 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php @@ -0,0 +1,122 @@ +createLoop(); + + $timer = $loop->addTimer(0.001, $this->expectCallableNever()); + + $this->assertInstanceOf('React\EventLoop\TimerInterface', $timer); + $this->assertFalse($timer->isPeriodic()); + } + + public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning() + { + $loop = $this->createLoop(); + + $loop->addTimer(0.001, $this->expectCallableOnce()); + + $start = microtime(true); + $loop->run(); + $end = microtime(true); + + // make no strict assumptions about actual time interval. + // must be at least 0.001s (1ms) and should not take longer than 0.1s + $this->assertGreaterThanOrEqual(0.001, $end - $start); + $this->assertLessThan(0.1, $end - $start); + } + + public function testAddPeriodicTimerReturnsPeriodicTimerInstance() + { + $loop = $this->createLoop(); + + $periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableNever()); + + $this->assertInstanceOf('React\EventLoop\TimerInterface', $periodic); + $this->assertTrue($periodic->isPeriodic()); + } + + public function testAddPeriodicTimerWillBeInvokedUntilItIsCancelled() + { + $loop = $this->createLoop(); + + $periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableExactly(3)); + + // make no strict assumptions about actual time interval. + // leave some room to ensure this ticks exactly 3 times. + $loop->addTimer(0.399, function () use ($loop, $periodic) { + $loop->cancelTimer($periodic); + }); + + $loop->run(); + } + + public function testAddPeriodicTimerWillBeInvokedWithMaximumAccuracyUntilItIsCancelled() + { + $loop = $this->createLoop(); + + $i = 0; + $periodic = $loop->addPeriodicTimer(0.001, function () use (&$i) { + ++$i; + }); + + $loop->addTimer(0.02, function () use ($loop, $periodic) { + $loop->cancelTimer($periodic); + }); + + $loop->run(); + + // make no strict assumptions about number of invocations. + // we know it must be no more than 20 times and should at least be + // invoked twice for really slow loops + $this->assertLessThanOrEqual(20, $i); + $this->assertGreaterThan(2, $i); + } + + public function testAddPeriodicTimerCancelsItself() + { + $loop = $this->createLoop(); + + $i = 0; + $loop->addPeriodicTimer(0.001, function ($timer) use (&$i, $loop) { + $i++; + + if ($i === 5) { + $loop->cancelTimer($timer); + } + }); + + $start = microtime(true); + $loop->run(); + $end = microtime(true); + + $this->assertEquals(5, $i); + + // make no strict assumptions about time interval. + // 5 invocations must take at least 0.005s (5ms) and should not take + // longer than 0.1s for slower loops. + $this->assertGreaterThanOrEqual(0.005, $end - $start); + $this->assertLessThan(0.1, $end - $start); + } + + public function testMinimumIntervalOneMicrosecond() + { + $loop = $this->createLoop(); + + $timer = $loop->addTimer(0, function () {}); + + $this->assertEquals(0.000001, $timer->getInterval()); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtEvTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtEvTimerTest.php new file mode 100644 index 0000000..bfa9186 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtEvTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.'); + } + + return new ExtEvLoop(); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php new file mode 100644 index 0000000..a7a6d00 --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('ext-event tests skipped because ext-event is not installed.'); + } + + return new ExtEventLoop(); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtLibevTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibevTimerTest.php new file mode 100644 index 0000000..65e82be --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibevTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('libev tests skipped because ext-libev is not installed.'); + } + + return new ExtLibevLoop(); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtLibeventTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibeventTimerTest.php new file mode 100644 index 0000000..9089b9a --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibeventTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('libevent tests skipped because ext-libevent is not installed.'); + } + + return new ExtLibeventLoop(); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php new file mode 100644 index 0000000..cfe1d7d --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php @@ -0,0 +1,13 @@ +tick(); + + // simulate a bunch of processing on stream events, + // part of which schedules a future timer... + sleep(1); + $timers->add(new Timer(0.5, function () { + $this->fail("Timer shouldn't be called"); + })); + + $timers->tick(); + + $this->assertTrue(true); + } +} diff --git a/assets/php/vendor/react/event-loop/tests/bootstrap.php b/assets/php/vendor/react/event-loop/tests/bootstrap.php new file mode 100644 index 0000000..ea7dd4c --- /dev/null +++ b/assets/php/vendor/react/event-loop/tests/bootstrap.php @@ -0,0 +1,15 @@ +addPsr4('React\\Tests\\EventLoop\\', __DIR__); + +if (!defined('SIGUSR1')) { + define('SIGUSR1', 1); +} + +if (!defined('SIGUSR2')) { + define('SIGUSR2', 2); +} diff --git a/assets/php/vendor/react/event-loop/travis-init.sh b/assets/php/vendor/react/event-loop/travis-init.sh new file mode 100755 index 0000000..29ce884 --- /dev/null +++ b/assets/php/vendor/react/event-loop/travis-init.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e +set -o pipefail + +if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && + "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then + + # install 'event' and 'ev' PHP extension + if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then + echo "yes" | pecl install event + echo "yes" | pecl install ev + fi + + # install 'libevent' PHP extension (does not support php 7) + if [[ "$TRAVIS_PHP_VERSION" != "7.0" && + "$TRAVIS_PHP_VERSION" != "7.1" && + "$TRAVIS_PHP_VERSION" != "7.2" ]]; then + curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz + pushd libevent-0.1.0 + phpize + ./configure + make + make install + popd + echo "extension=libevent.so" >> "$(php -r 'echo php_ini_loaded_file();')" + fi + + # install 'libev' PHP extension (does not support php 7) + if [[ "$TRAVIS_PHP_VERSION" != "7.0" && + "$TRAVIS_PHP_VERSION" != "7.1" && + "$TRAVIS_PHP_VERSION" != "7.2" ]]; then + git clone --recursive https://github.com/m4rw3r/php-libev + pushd php-libev + phpize + ./configure --with-libev + make + make install + popd + echo "extension=libev.so" >> "$(php -r 'echo php_ini_loaded_file();')" + fi + +fi diff --git a/assets/php/vendor/react/promise-timer/.gitignore b/assets/php/vendor/react/promise-timer/.gitignore new file mode 100644 index 0000000..de4a392 --- /dev/null +++ b/assets/php/vendor/react/promise-timer/.gitignore @@ -0,0 +1,2 @@ +/vendor +/composer.lock diff --git a/assets/php/vendor/react/promise-timer/.travis.yml b/assets/php/vendor/react/promise-timer/.travis.yml new file mode 100644 index 0000000..a71864a --- /dev/null +++ b/assets/php/vendor/react/promise-timer/.travis.yml @@ -0,0 +1,26 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - hhvm # ignore errors, see below + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + allow_failures: + - php: hhvm + +install: + - composer install --no-interaction + +script: + - vendor/bin/phpunit --coverage-text diff --git a/assets/php/vendor/react/promise-timer/CHANGELOG.md b/assets/php/vendor/react/promise-timer/CHANGELOG.md new file mode 100644 index 0000000..0a21244 --- /dev/null +++ b/assets/php/vendor/react/promise-timer/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +## 1.2.1 (2017-12-22) + +* README improvements + (#28 by @jsor) + +* Improve test suite by adding forward compatiblity with PHPUnit 6 and + fix test suite forward compatibility with upcoming EventLoop releases + (#30 and #31 by @clue) + +## 1.2.0 (2017-08-08) + +* Feature: Only start timers if input Promise is still pending and + return a settled output promise if the input is already settled. + (#25 by @clue) + +* Feature: Cap minimum timer interval at 1µs across all versions + (#23 by @clue) + +* Feature: Forward compatibility with EventLoop v1.0 and v0.5 + (#27 by @clue) + +* Improve test suite by adding PHPUnit to require-dev and + lock Travis distro so new defaults will not break the build + (#24 and #26 by @clue) + +## 1.1.1 (2016-12-27) + +* Improve test suite to use PSR-4 autoloader and proper namespaces. + (#21 by @clue) + +## 1.1.0 (2016-02-29) + +* Feature: Support promise cancellation for all timer primitives + (#18 by @clue) + +## 1.0.0 (2015-09-29) + +* First tagged release diff --git a/assets/php/vendor/react/promise-timer/LICENSE b/assets/php/vendor/react/promise-timer/LICENSE new file mode 100644 index 0000000..dc09d1e --- /dev/null +++ b/assets/php/vendor/react/promise-timer/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Christian Lück + +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/assets/php/vendor/react/promise-timer/README.md b/assets/php/vendor/react/promise-timer/README.md new file mode 100644 index 0000000..2ea94fa --- /dev/null +++ b/assets/php/vendor/react/promise-timer/README.md @@ -0,0 +1,372 @@ +# PromiseTimer + +[![Build Status](https://travis-ci.org/reactphp/promise-timer.svg?branch=master)](https://travis-ci.org/reactphp/promise-timer) + +A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](https://reactphp.org/). + +**Table of contents** + +* [Usage](#usage) + * [timeout()](#timeout) + * [Timeout cancellation](#timeout-cancellation) + * [Cancellation handler](#cancellation-handler) + * [Input cancellation](#input-cancellation) + * [Output cancellation](#output-cancellation) + * [Collections](#collections) + * [resolve()](#resolve) + * [Resolve cancellation](#resolve-cancellation) + * [reject()](#reject) + * [Reject cancellation](#reject-cancellation) + * [TimeoutException](#timeoutexception) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Usage + +This lightweight library consists only of a few simple functions. +All functions reside under the `React\Promise\Timer` namespace. + +The below examples assume you use an import statement similar to this: + +```php +use React\Promise\Timer; + +Timer\timeout(…); +``` + +Alternatively, you can also refer to them with their fully-qualified name: + +```php +\React\Promise\Timer\timeout(…); +``` + +### timeout() + +The `timeout(PromiseInterface $promise, $time, LoopInterface $loop)` function +can be used to *cancel* operations that take *too long*. +You need to pass in an input `$promise` that represents a pending operation and timeout parameters. +It returns a new `Promise` with the following resolution behavior: + +* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value. +* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value. +* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception). + +Internally, the given `$time` value will be used to start a timer that will +*cancel* the pending operation once it triggers. +This implies that if you pass a really small (or negative) value, it will still +start a timer and will thus trigger at the earliest possible time in the future. + +If the input `$promise` is already settled, then the resulting promise will +resolve or reject immediately without starting a timer at all. + +A common use case for handling only resolved values looks like this: + +```php +$promise = accessSomeRemoteResource(); +Timer\timeout($promise, 10.0, $loop)->then(function ($value) { + // the operation finished within 10.0 seconds +}); +``` + +A more complete example could look like this: + +```php +$promise = accessSomeRemoteResource(); +Timer\timeout($promise, 10.0, $loop)->then( + function ($value) { + // the operation finished within 10.0 seconds + }, + function ($error) { + if ($error instanceof Timer\TimeoutException) { + // the operation has failed due to a timeout + } else { + // the input operation has failed due to some other error + } + } +); +``` + +Or if you're using [react/promise v2.2.0](https://github.com/reactphp/promise) or up: + +```php +Timer\timeout($promise, 10.0, $loop) + ->then(function ($value) { + // the operation finished within 10.0 seconds + }) + ->otherwise(function (Timer\TimeoutException $error) { + // the operation has failed due to a timeout + }) + ->otherwise(function ($error) { + // the input operation has failed due to some other error + }) +; +``` + +#### Timeout cancellation + +As discussed above, the [`timeout()`](#timeout) function will *cancel* the +underlying operation if it takes *too long*. +This means that you can be sure the resulting promise will then be rejected +with a [`TimeoutException`](#timeoutexception). + +However, what happens to the underlying input `$promise` is a bit more tricky: +Once the timer fires, we will try to call +[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel) +on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler). + +This means that it's actually up the input `$promise` to handle +[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface). + +* A common use case involves cleaning up any resources like open network sockets or + file handles or terminating external processes or timers. + +* If the given input `$promise` does not support cancellation, then this is a NO-OP. + This means that while the resulting promise will still be rejected, the underlying + input `$promise` may still be pending and can hence continue consuming resources. + +See the following chapter for more details on the cancellation handler. + +#### Cancellation handler + +For example, an implementation for the above operation could look like this: + +```php +function accessSomeRemoteResource() +{ + return new Promise( + function ($resolve, $reject) use (&$socket) { + // this will be called once the promise is created + // a common use case involves opening any resources and eventually resolving + $socket = createSocket(); + $socket->on('data', function ($data) use ($resolve) { + $resolve($data); + }); + }, + function ($resolve, $reject) use (&$socket) { + // this will be called once calling `cancel()` on this promise + // a common use case involves cleaning any resources and then rejecting + $socket->close(); + $reject(new \RuntimeException('Operation cancelled')); + } + ); +} +``` + +In this example, calling `$promise->cancel()` will invoke the registered cancellation +handler which then closes the network socket and rejects the `Promise` instance. + +If no cancellation handler is passed to the `Promise` constructor, then invoking +its `cancel()` method it is effectively a NO-OP. +This means that it may still be pending and can hence continue consuming resources. + +For more details on the promise cancellation, please refer to the +[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface). + +#### Input cancellation + +Irrespective of the timout handling, you can also explicitly `cancel()` the +input `$promise` at any time. +This means that the `timeout()` handling does not affect cancellation of the +input `$promise`, as demonstrated in the following example: + +```php +$promise = accessSomeRemoteResource(); +$timeout = Timer\timeout($promise, 10.0, $loop); + +$promise->cancel(); +``` + +The registered [cancellation handler](#cancellation-handler) is responsible for +handling the `cancel()` call: + +* A described above, a common use involves resource cleanup and will then *reject* + the `Promise`. + If the input `$promise` is being rejected, then the timeout will be aborted + and the resulting promise will also be rejected. +* If the input `$promise` is still pending, then the timout will continue + running until the timer expires. + The same happens if the input `$promise` does not register a + [cancellation handler](#cancellation-handler). + +#### Output cancellation + +Similarily, you can also explicitly `cancel()` the resulting promise like this: + +```php +$promise = accessSomeRemoteResource(); +$timeout = Timer\timeout($promise, 10.0, $loop); + +$timeout->cancel(); +``` + +Note how this looks very similar to the above [input cancellation](#input-cancellation) +example. Accordingly, it also behaves very similar. + +Calling `cancel()` on the resulting promise will merely try +to `cancel()` the input `$promise`. +This means that we do not take over responsibility of the outcome and it's +entirely up to the input `$promise` to handle cancellation support. + +The registered [cancellation handler](#cancellation-handler) is responsible for +handling the `cancel()` call: + +* As described above, a common use involves resource cleanup and will then *reject* + the `Promise`. + If the input `$promise` is being rejected, then the timeout will be aborted + and the resulting promise will also be rejected. +* If the input `$promise` is still pending, then the timout will continue + running until the timer expires. + The same happens if the input `$promise` does not register a + [cancellation handler](#cancellation-handler). + +To re-iterate, note that calling `cancel()` on the resulting promise will merely +try to cancel the input `$promise` only. +It is then up to the cancellation handler of the input promise to settle the promise. +If the input promise is still pending when the timeout occurs, then the normal +[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejecting +the output promise with a [`TimeoutException`](#timeoutexception). + +This is done for consistency with the [timeout cancellation](#timeout-cancellation) +handling and also because it is assumed this is often used like this: + +```php +$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop); + +$timeout->cancel(); +``` + +As described above, this example works as expected and cleans up any resources +allocated for the input `$promise`. + +Note that if the given input `$promise` does not support cancellation, then this +is a NO-OP. +This means that while the resulting promise will still be rejected after the +timeout, the underlying input `$promise` may still be pending and can hence +continue consuming resources. + +#### Collections + +If you want to wait for multiple promises to resolve, you can use the normal promise primitives like this: + +```php +$promises = array( + accessSomeRemoteResource(), + accessSomeRemoteResource(), + accessSomeRemoteResource() +); + +$promise = \React\Promise\all($promises); + +Timer\timeout($promise, 10, $loop)->then(function ($values) { + // *all* promises resolved +}); +``` + +The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc. + +For more details on the promise primitives, please refer to the +[Promise documentation](https://github.com/reactphp/promise#functions). + +### resolve() + +The `resolve($time, LoopInterface $loop)` function can be used to create a new Promise that +resolves in `$time` seconds with the `$time` as the fulfillment value. + +```php +Timer\resolve(1.5, $loop)->then(function ($time) { + echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL; +}); +``` + +Internally, the given `$time` value will be used to start a timer that will +resolve the promise once it triggers. +This implies that if you pass a really small (or negative) value, it will still +start a timer and will thus trigger at the earliest possible time in the future. + +#### Resolve cancellation + +You can explicitly `cancel()` the resulting timer promise at any time: + +```php +$timer = Timer\resolve(2.0, $loop); + +$timer->cancel(); +``` + +This will abort the timer and *reject* with a `RuntimeException`. + +### reject() + +The `reject($time, LoopInterface $loop)` function can be used to create a new Promise +which rejects in `$time` seconds with a `TimeoutException`. + +```php +Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) { + echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL; +}); +``` + +Internally, the given `$time` value will be used to start a timer that will +reject the promise once it triggers. +This implies that if you pass a really small (or negative) value, it will still +start a timer and will thus trigger at the earliest possible time in the future. + +This function complements the [`resolve()`](#resolve) function +and can be used as a basic building block for higher-level promise consumers. + +#### Reject cancellation + +You can explicitly `cancel()` the resulting timer promise at any time: + +```php +$timer = Timer\reject(2.0, $loop); + +$timer->cancel(); +``` + +This will abort the timer and *reject* with a `RuntimeException`. + +### TimeoutException + +The `TimeoutException` extends PHP's built-in `RuntimeException`. + +The `getTimeout()` method can be used to get the timeout value in seconds. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](http://semver.org/). +This will install the latest supported version: + +```bash +$ composer require react/promise-timer:^1.2.1 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and +HHVM. +It's *highly recommended to use PHP 7+* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +## License + +MIT, see [LICENSE file](LICENSE). diff --git a/assets/php/vendor/react/promise-timer/composer.json b/assets/php/vendor/react/promise-timer/composer.json new file mode 100644 index 0000000..e425dc6 --- /dev/null +++ b/assets/php/vendor/react/promise-timer/composer.json @@ -0,0 +1,28 @@ +{ + "name": "react/promise-timer", + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "keywords": ["Promise", "timeout", "timer", "event-loop", "ReactPHP", "async"], + "homepage": "https://github.com/react/promise-timer", + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "autoload": { + "psr-4": { "React\\Promise\\Timer\\": "src/" }, + "files": [ "src/functions.php" ] + }, + "autoload-dev": { + "psr-4": { "React\\Tests\\Promise\\Timer\\": "tests/" } + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "~2.1|~1.2" + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + } +} diff --git a/assets/php/vendor/react/promise-timer/phpunit.xml.dist b/assets/php/vendor/react/promise-timer/phpunit.xml.dist new file mode 100644 index 0000000..bb79fba --- /dev/null +++ b/assets/php/vendor/react/promise-timer/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + ./tests/ + + + + + ./src/ + + + \ No newline at end of file diff --git a/assets/php/vendor/react/promise-timer/src/TimeoutException.php b/assets/php/vendor/react/promise-timer/src/TimeoutException.php new file mode 100644 index 0000000..18ea72f --- /dev/null +++ b/assets/php/vendor/react/promise-timer/src/TimeoutException.php @@ -0,0 +1,22 @@ +timeout = $timeout; + } + + public function getTimeout() + { + return $this->timeout; + } +} diff --git a/assets/php/vendor/react/promise-timer/src/functions.php b/assets/php/vendor/react/promise-timer/src/functions.php new file mode 100644 index 0000000..6ad9867 --- /dev/null +++ b/assets/php/vendor/react/promise-timer/src/functions.php @@ -0,0 +1,70 @@ +then(function ($v) use (&$timer, $loop, $resolve) { + if ($timer) { + $loop->cancelTimer($timer); + } + $timer = false; + $resolve($v); + }, function ($v) use (&$timer, $loop, $reject) { + if ($timer) { + $loop->cancelTimer($timer); + } + $timer = false; + $reject($v); + }); + + // promise already resolved => no need to start timer + if ($timer === false) { + return; + } + + // start timeout timer which will cancel the input promise + $timer = $loop->addTimer($time, function () use ($time, $promise, $reject) { + $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds')); + + if ($promise instanceof CancellablePromiseInterface) { + $promise->cancel(); + } + }); + }, $canceller); +} + +function resolve($time, LoopInterface $loop) +{ + return new Promise(function ($resolve) use ($loop, $time, &$timer) { + // resolve the promise when the timer fires in $time seconds + $timer = $loop->addTimer($time, function () use ($time, $resolve) { + $resolve($time); + }); + }, function ($resolveUnused, $reject) use (&$timer, $loop) { + // cancelling this promise will cancel the timer and reject + $loop->cancelTimer($timer); + $reject(new \RuntimeException('Timer cancelled')); + }); +} + +function reject($time, LoopInterface $loop) +{ + return resolve($time, $loop)->then(function ($time) { + throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds'); + }); +} diff --git a/assets/php/vendor/react/promise-timer/tests/CallableStub.php b/assets/php/vendor/react/promise-timer/tests/CallableStub.php new file mode 100644 index 0000000..a391aa5 --- /dev/null +++ b/assets/php/vendor/react/promise-timer/tests/CallableStub.php @@ -0,0 +1,10 @@ +loop); + + $this->expectPromisePending($promise); + } + + public function testPromiseExpiredIsPendingWithoutRunningLoop() + { + $promise = Timer\reject(-1, $this->loop); + + $this->expectPromisePending($promise); + } + + public function testPromiseWillBeRejectedOnTimeout() + { + $promise = Timer\reject(0.01, $this->loop); + + $this->loop->run(); + + $this->expectPromiseRejected($promise); + } + + public function testPromiseExpiredWillBeRejectedOnTimeout() + { + $promise = Timer\reject(-1, $this->loop); + + $this->loop->run(); + + $this->expectPromiseRejected($promise); + } + + public function testCancelingPromiseWillRejectTimer() + { + $promise = Timer\reject(0.01, $this->loop); + + $promise->cancel(); + + $this->expectPromiseRejected($promise); + } +} diff --git a/assets/php/vendor/react/promise-timer/tests/FunctionResolveTest.php b/assets/php/vendor/react/promise-timer/tests/FunctionResolveTest.php new file mode 100644 index 0000000..0bfdc21 --- /dev/null +++ b/assets/php/vendor/react/promise-timer/tests/FunctionResolveTest.php @@ -0,0 +1,71 @@ +loop); + + $this->expectPromisePending($promise); + } + + public function testPromiseExpiredIsPendingWithoutRunningLoop() + { + $promise = Timer\resolve(-1, $this->loop); + + $this->expectPromisePending($promise); + } + + public function testPromiseWillBeResolvedOnTimeout() + { + $promise = Timer\resolve(0.01, $this->loop); + + $this->loop->run(); + + $this->expectPromiseResolved($promise); + } + + public function testPromiseExpiredWillBeResolvedOnTimeout() + { + $promise = Timer\resolve(-1, $this->loop); + + $this->loop->run(); + + $this->expectPromiseResolved($promise); + } + + public function testWillStartLoopTimer() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('addTimer')->with($this->equalTo(0.01)); + + Timer\resolve(0.01, $loop); + } + + public function testCancellingPromiseWillCancelLoopTimer() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $timer = $this->getMockBuilder(interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface')->getMock(); + $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer)); + + $promise = Timer\resolve(0.01, $loop); + + $loop->expects($this->once())->method('cancelTimer')->with($this->equalTo($timer)); + + $promise->cancel(); + } + + public function testCancelingPromiseWillRejectTimer() + { + $promise = Timer\resolve(0.01, $this->loop); + + $promise->cancel(); + + $this->expectPromiseRejected($promise); + } +} diff --git a/assets/php/vendor/react/promise-timer/tests/FunctionTimeoutTest.php b/assets/php/vendor/react/promise-timer/tests/FunctionTimeoutTest.php new file mode 100644 index 0000000..aaca2da --- /dev/null +++ b/assets/php/vendor/react/promise-timer/tests/FunctionTimeoutTest.php @@ -0,0 +1,169 @@ +loop); + + $this->expectPromiseResolved($promise); + } + + public function testResolvedExpiredWillResolveRightAway() + { + $promise = Promise\resolve(); + + $promise = Timer\timeout($promise, -1, $this->loop); + + $this->expectPromiseResolved($promise); + } + + public function testResolvedWillNotStartTimer() + { + $promise = Promise\resolve(); + + Timer\timeout($promise, 3, $this->loop); + + $time = microtime(true); + $this->loop->run(); + $time = microtime(true) - $time; + + $this->assertLessThan(0.5, $time); + } + + public function testRejectedWillRejectRightAway() + { + $promise = Promise\reject(); + + $promise = Timer\timeout($promise, 3, $this->loop); + + $this->expectPromiseRejected($promise); + } + + public function testRejectedWillNotStartTimer() + { + $promise = Promise\reject(); + + Timer\timeout($promise, 3, $this->loop); + + $time = microtime(true); + $this->loop->run(); + $time = microtime(true) - $time; + + $this->assertLessThan(0.5, $time); + } + + public function testPendingWillRejectOnTimeout() + { + $promise = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock(); + + $promise = Timer\timeout($promise, 0.01, $this->loop); + + $this->loop->run(); + + $this->expectPromiseRejected($promise); + } + + public function testPendingCancellableWillBeCancelledOnTimeout() + { + $promise = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock(); + $promise->expects($this->once())->method('cancel'); + + Timer\timeout($promise, 0.01, $this->loop); + + $this->loop->run(); + } + + public function testCancelTimeoutWithoutCancellationhandlerWillNotCancelTimerAndWillNotReject() + { + $promise = new \React\Promise\Promise(function () { }); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $timer = $this->getMockBuilder('React\EventLoop\Timer\TimerInterface')->getMock(); + $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer)); + $loop->expects($this->never())->method('cancelTimer'); + + $timeout = Timer\timeout($promise, 0.01, $loop); + + $timeout->cancel(); + + $this->expectPromisePending($timeout); + } + + public function testResolvedPromiseWillNotStartTimer() + { + $promise = new \React\Promise\Promise(function ($resolve) { $resolve(true); }); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->never())->method('addTimer'); + + $timeout = Timer\timeout($promise, 0.01, $loop); + + $this->expectPromiseResolved($timeout); + } + + public function testRejectedPromiseWillNotStartTimer() + { + $promise = Promise\reject(new \RuntimeException()); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->never())->method('addTimer'); + + $timeout = Timer\timeout($promise, 0.01, $loop); + + $this->expectPromiseRejected($timeout); + } + + public function testCancelTimeoutWillCancelGivenPromise() + { + $promise = new \React\Promise\Promise(function () { }, $this->expectCallableOnce()); + + $timeout = Timer\timeout($promise, 0.01, $this->loop); + + $timeout->cancel(); + } + + public function testCancelGivenPromiseWillReject() + { + $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); }); + + $timeout = Timer\timeout($promise, 0.01, $this->loop); + + $promise->cancel(); + + $this->expectPromiseRejected($promise); + $this->expectPromiseRejected($timeout); + } + + public function testCancelTimeoutWillRejectIfGivenPromiseWillReject() + { + $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); }); + + $timeout = Timer\timeout($promise, 0.01, $this->loop); + + $timeout->cancel(); + + $this->expectPromiseRejected($promise); + $this->expectPromiseRejected($timeout); + } + + public function testCancelTimeoutWillResolveIfGivenPromiseWillResolve() + { + $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $resolve(); }); + + $timeout = Timer\timeout($promise, 0.01, $this->loop); + + $timeout->cancel(); + + $this->expectPromiseResolved($promise); + $this->expectPromiseResolved($timeout); + } +} diff --git a/assets/php/vendor/react/promise-timer/tests/TestCase.php b/assets/php/vendor/react/promise-timer/tests/TestCase.php new file mode 100644 index 0000000..9d8d49a --- /dev/null +++ b/assets/php/vendor/react/promise-timer/tests/TestCase.php @@ -0,0 +1,61 @@ +loop = Factory::create(); + } + + protected function expectCallableOnce() + { + $mock = $this->createCallableMock(); + + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + /** + * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react) + */ + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\Promise\Timer\CallableStub')->getMock(); + } + + protected function expectPromiseRejected($promise) + { + return $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } + + protected function expectPromiseResolved($promise) + { + return $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); + } + + protected function expectPromisePending($promise) + { + return $promise->then($this->expectCallableNever(), $this->expectCallableNever()); + } +} diff --git a/assets/php/vendor/react/promise-timer/tests/TimeoutExceptionTest.php b/assets/php/vendor/react/promise-timer/tests/TimeoutExceptionTest.php new file mode 100644 index 0000000..e9bedd9 --- /dev/null +++ b/assets/php/vendor/react/promise-timer/tests/TimeoutExceptionTest.php @@ -0,0 +1,15 @@ +assertEquals(10, $e->getTimeout()); + } +} diff --git a/assets/php/vendor/react/promise/.gitignore b/assets/php/vendor/react/promise/.gitignore new file mode 100644 index 0000000..5241c60 --- /dev/null +++ b/assets/php/vendor/react/promise/.gitignore @@ -0,0 +1,5 @@ +composer.lock +composer.phar +phpunit.xml +build/ +vendor/ diff --git a/assets/php/vendor/react/promise/.travis.yml b/assets/php/vendor/react/promise/.travis.yml new file mode 100644 index 0000000..5d0c6ab --- /dev/null +++ b/assets/php/vendor/react/promise/.travis.yml @@ -0,0 +1,22 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - nightly + - hhvm + +before_install: + - composer self-update + +install: + - composer install + +script: + - ./vendor/bin/phpunit -v --coverage-text --coverage-clover=./build/logs/clover.xml + +after_script: + - if [ -f ./build/logs/clover.xml ]; then travis_retry composer require satooshi/php-coveralls --no-interaction --update-with-dependencies; fi + - if [ -f ./build/logs/clover.xml ]; then php vendor/bin/coveralls -v; fi diff --git a/assets/php/vendor/react/promise/CHANGELOG.md b/assets/php/vendor/react/promise/CHANGELOG.md new file mode 100644 index 0000000..484e542 --- /dev/null +++ b/assets/php/vendor/react/promise/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG for 2.x +================= + +* 2.5.1 (2017-03-25) + + * Fix circular references when resolving with a promise which follows + itself (#94). + +* 2.5.0 (2016-12-22) + + * Revert automatic cancellation of pending collection promises once the + output promise resolves. This was introduced in 42d86b7 (PR #36, released + in [v2.3.0](https://github.com/reactphp/promise/releases/tag/v2.3.0)) and + was both unintended and backward incompatible. + + If you need automatic cancellation, you can use something like: + + ```php + function allAndCancel(array $promises) + { + return \React\Promise\all($promises) + ->always(function() use ($promises) { + foreach ($promises as $promise) { + if ($promise instanceof \React\Promise\CancellablePromiseInterface) { + $promise->cancel(); + } + } + }); + } + ``` + * `all()` and `map()` functions now preserve the order of the array (#77). + * Fix circular references when resolving a promise with itself (#71). + +* 2.4.1 (2016-05-03) + + * Fix `some()` not cancelling pending promises when too much input promises + reject (16ff799). + +* 2.4.0 (2016-03-31) + + * Support foreign thenables in `resolve()`. + Any object that provides a `then()` method is now assimilated to a trusted + promise that follows the state of this thenable (#52). + * Fix `some()` and `any()` for input arrays containing not enough items + (#34). + +* 2.3.0 (2016-03-24) + + * Allow cancellation of promises returned by functions working on promise + collections (#36). + * Handle `\Throwable` in the same way as `\Exception` (#51 by @joshdifabio). + +* 2.2.2 (2016-02-26) + + * Fix cancellation handlers called multiple times (#47 by @clue). + +* 2.2.1 (2015-07-03) + + * Fix stack error when resolving a promise in its own fulfillment or + rejection handlers. + +* 2.2.0 (2014-12-30) + + * Introduce new `ExtendedPromiseInterface` implemented by all promises. + * Add new `done()` method (part of the `ExtendedPromiseInterface`). + * Add new `otherwise()` method (part of the `ExtendedPromiseInterface`). + * Add new `always()` method (part of the `ExtendedPromiseInterface`). + * Add new `progress()` method (part of the `ExtendedPromiseInterface`). + * Rename `Deferred::progress` to `Deferred::notify` to avoid confusion with + `ExtendedPromiseInterface::progress` (a `Deferred::progress` alias is + still available for backward compatibility) + * `resolve()` now always returns a `ExtendedPromiseInterface`. + +* 2.1.0 (2014-10-15) + + * Introduce new `CancellablePromiseInterface` implemented by all promises. + * Add new `cancel()` method (part of the `CancellablePromiseInterface`). + +* 2.0.0 (2013-12-10) + + New major release. The goal is to streamline the API and to make it more + compliant with other promise libraries and especially with the new upcoming + [ES6 promises specification](https://github.com/domenic/promises-unwrapping/). + + * Add standalone Promise class. + * Add new `race()` function. + * BC break: Bump minimum PHP version to PHP 5.4. + * BC break: Remove `ResolverInterface` and `PromiseInterface` from + `Deferred`. + * BC break: Change signature of `PromiseInterface`. + * BC break: Remove `When` and `Util` classes and move static methods to + functions. + * BC break: `FulfilledPromise` and `RejectedPromise` now throw an exception + when initialized with a promise instead of a value/reason. + * BC break: `Deferred::resolve()` and `Deferred::reject()` no longer return + a promise. diff --git a/assets/php/vendor/react/promise/LICENSE b/assets/php/vendor/react/promise/LICENSE new file mode 100644 index 0000000..5919d20 --- /dev/null +++ b/assets/php/vendor/react/promise/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012-2016 Jan Sorgalla + +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/assets/php/vendor/react/promise/README.md b/assets/php/vendor/react/promise/README.md new file mode 100644 index 0000000..9c0558c --- /dev/null +++ b/assets/php/vendor/react/promise/README.md @@ -0,0 +1,840 @@ +React/Promise +============= + +A lightweight implementation of +[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. + +[![Build Status](https://travis-ci.org/reactphp/promise.svg?branch=master)](http://travis-ci.org/reactphp/promise) +[![Coverage Status](https://coveralls.io/repos/github/reactphp/promise/badge.svg?branch=master)](https://coveralls.io/github/reactphp/promise?branch=master) + +Table of Contents +----------------- + +1. [Introduction](#introduction) +2. [Concepts](#concepts) + * [Deferred](#deferred) + * [Promise](#promise) +3. [API](#api) + * [Deferred](#deferred-1) + * [Deferred::promise()](#deferredpromise) + * [Deferred::resolve()](#deferredresolve) + * [Deferred::reject()](#deferredreject) + * [Deferred::notify()](#deferrednotify) + * [PromiseInterface](#promiseinterface) + * [PromiseInterface::then()](#promiseinterfacethen) + * [ExtendedPromiseInterface](#extendedpromiseinterface) + * [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone) + * [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise) + * [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways) + * [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress) + * [CancellablePromiseInterface](#cancellablepromiseinterface) + * [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel) + * [Promise](#promise-1) + * [FulfilledPromise](#fulfilledpromise) + * [RejectedPromise](#rejectedpromise) + * [LazyPromise](#lazypromise) + * [Functions](#functions) + * [resolve()](#resolve) + * [reject()](#reject) + * [all()](#all) + * [race()](#race) + * [any()](#any) + * [some()](#some) + * [map()](#map) + * [reduce()](#reduce) + * [PromisorInterface](#promisorinterface) +4. [Examples](#examples) + * [How to use Deferred](#how-to-use-deferred) + * [How promise forwarding works](#how-promise-forwarding-works) + * [Resolution forwarding](#resolution-forwarding) + * [Rejection forwarding](#rejection-forwarding) + * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding) + * [Progress event forwarding](#progress-event-forwarding) + * [done() vs. then()](#done-vs-then) +5. [Credits](#credits) +6. [License](#license) + +Introduction +------------ + +React/Promise is a library implementing +[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. + +It also provides several other useful promise-related concepts, such as joining +multiple promises and mapping and reducing collections of promises. + +If you've never heard about promises before, +[read this first](https://gist.github.com/3889970). + +Concepts +-------- + +### Deferred + +A **Deferred** represents a computation or unit of work that may not have +completed yet. Typically (but not always), that computation will be something +that executes asynchronously and completes at some point in the future. + +### Promise + +While a deferred represents the computation itself, a **Promise** represents +the result of that computation. Thus, each deferred has a promise that acts as +a placeholder for its actual result. + +API +--- + +### Deferred + +A deferred represents an operation whose resolution is pending. It has separate +promise and resolver parts. + +```php +$deferred = new React\Promise\Deferred(); + +$promise = $deferred->promise(); + +$deferred->resolve(mixed $value = null); +$deferred->reject(mixed $reason = null); +$deferred->notify(mixed $update = null); +``` + +The `promise` method returns the promise of the deferred. + +The `resolve` and `reject` methods control the state of the deferred. + +The `notify` method is for progress notification. + +The constructor of the `Deferred` accepts an optional `$canceller` argument. +See [Promise](#promise-1) for more information. + +#### Deferred::promise() + +```php +$promise = $deferred->promise(); +``` + +Returns the promise of the deferred, which you can hand out to others while +keeping the authority to modify its state to yourself. + +#### Deferred::resolve() + +```php +$deferred->resolve(mixed $value = null); +``` + +Resolves the promise returned by `promise()`. All consumers are notified by +having `$onFulfilled` (which they registered via `$promise->then()`) called with +`$value`. + +If `$value` itself is a promise, the promise will transition to the state of +this promise once it is resolved. + +#### Deferred::reject() + +```php +$deferred->reject(mixed $reason = null); +``` + +Rejects the promise returned by `promise()`, signalling that the deferred's +computation failed. +All consumers are notified by having `$onRejected` (which they registered via +`$promise->then()`) called with `$reason`. + +If `$reason` itself is a promise, the promise will be rejected with the outcome +of this promise regardless whether it fulfills or rejects. + +#### Deferred::notify() + +```php +$deferred->notify(mixed $update = null); +``` + +Triggers progress notifications, to indicate to consumers that the computation +is making progress toward its result. + +All consumers are notified by having `$onProgress` (which they registered via +`$promise->then()`) called with `$update`. + +### PromiseInterface + +The promise interface provides the common interface for all promise +implementations. + +A promise represents an eventual outcome, which is either fulfillment (success) +and an associated value, or rejection (failure) and an associated reason. + +Once in the fulfilled or rejected state, a promise becomes immutable. +Neither its state nor its result (or error) can be modified. + +#### Implementations + +* [Promise](#promise-1) +* [FulfilledPromise](#fulfilledpromise) +* [RejectedPromise](#rejectedpromise) +* [LazyPromise](#lazypromise) + +#### PromiseInterface::then() + +```php +$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null); +``` + +Transforms a promise's value by applying a function to the promise's fulfillment +or rejection value. Returns a new promise for the transformed result. + +The `then()` method registers new fulfilled, rejection and progress handlers +with a promise (all parameters are optional): + + * `$onFulfilled` will be invoked once the promise is fulfilled and passed + the result as the first argument. + * `$onRejected` will be invoked once the promise is rejected and passed the + reason as the first argument. + * `$onProgress` will be invoked whenever the producer of the promise + triggers progress notifications and passed a single argument (whatever it + wants) to indicate progress. + +It returns a new promise that will fulfill with the return value of either +`$onFulfilled` or `$onRejected`, whichever is called, or will reject with +the thrown exception if either throws. + +A promise makes the following guarantees about handlers registered in +the same call to `then()`: + + 1. Only one of `$onFulfilled` or `$onRejected` will be called, + never both. + 2. `$onFulfilled` and `$onRejected` will never be called more + than once. + 3. `$onProgress` may be called multiple times. + +#### See also + +* [resolve()](#resolve) - Creating a resolved promise +* [reject()](#reject) - Creating a rejected promise +* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone) +* [done() vs. then()](#done-vs-then) + +### ExtendedPromiseInterface + +The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut +and utility methods which are not part of the Promises/A specification. + +#### Implementations + +* [Promise](#promise-1) +* [FulfilledPromise](#fulfilledpromise) +* [RejectedPromise](#rejectedpromise) +* [LazyPromise](#lazypromise) + +#### ExtendedPromiseInterface::done() + +```php +$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null); +``` + +Consumes the promise's ultimate value if the promise fulfills, or handles the +ultimate error. + +It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or +return a rejected promise. + +Since the purpose of `done()` is consumption rather than transformation, +`done()` always returns `null`. + +#### See also + +* [PromiseInterface::then()](#promiseinterfacethen) +* [done() vs. then()](#done-vs-then) + +#### ExtendedPromiseInterface::otherwise() + +```php +$promise->otherwise(callable $onRejected); +``` + +Registers a rejection handler for promise. It is a shortcut for: + +```php +$promise->then(null, $onRejected); +``` + +Additionally, you can type hint the `$reason` argument of `$onRejected` to catch +only specific errors. + +```php +$promise + ->otherwise(function (\RuntimeException $reason) { + // Only catch \RuntimeException instances + // All other types of errors will propagate automatically + }) + ->otherwise(function ($reason) { + // Catch other errors + )}; +``` + +#### ExtendedPromiseInterface::always() + +```php +$newPromise = $promise->always(callable $onFulfilledOrRejected); +``` + +Allows you to execute "cleanup" type tasks in a promise chain. + +It arranges for `$onFulfilledOrRejected` to be called, with no arguments, +when the promise is either fulfilled or rejected. + +* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, + `$newPromise` will fulfill with the same value as `$promise`. +* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a + rejected promise, `$newPromise` will reject with the thrown exception or + rejected promise's reason. +* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, + `$newPromise` will reject with the same reason as `$promise`. +* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a + rejected promise, `$newPromise` will reject with the thrown exception or + rejected promise's reason. + +`always()` behaves similarly to the synchronous finally statement. When combined +with `otherwise()`, `always()` allows you to write code that is similar to the familiar +synchronous catch/finally pair. + +Consider the following synchronous code: + +```php +try { + return doSomething(); +} catch(\Exception $e) { + return handleError($e); +} finally { + cleanup(); +} +``` + +Similar asynchronous code (with `doSomething()` that returns a promise) can be +written: + +```php +return doSomething() + ->otherwise('handleError') + ->always('cleanup'); +``` + +#### ExtendedPromiseInterface::progress() + +```php +$promise->progress(callable $onProgress); +``` + +Registers a handler for progress updates from promise. It is a shortcut for: + +```php +$promise->then(null, null, $onProgress); +``` + +### CancellablePromiseInterface + +A cancellable promise provides a mechanism for consumers to notify the creator +of the promise that they are not longer interested in the result of an +operation. + +#### CancellablePromiseInterface::cancel() + +``` php +$promise->cancel(); +``` + +The `cancel()` method notifies the creator of the promise that there is no +further interest in the results of the operation. + +Once a promise is settled (either fulfilled or rejected), calling `cancel()` on +a promise has no effect. + +#### Implementations + +* [Promise](#promise-1) +* [FulfilledPromise](#fulfilledpromise) +* [RejectedPromise](#rejectedpromise) +* [LazyPromise](#lazypromise) + +### Promise + +Creates a promise whose state is controlled by the functions passed to +`$resolver`. + +```php +$resolver = function (callable $resolve, callable $reject, callable $notify) { + // Do some work, possibly asynchronously, and then + // resolve or reject. You can notify of progress events + // along the way if you want/need. + + $resolve($awesomeResult); + // or $resolve($anotherPromise); + // or $reject($nastyError); + // or $notify($progressNotification); +}; + +$canceller = function (callable $resolve, callable $reject, callable $progress) { + // Cancel/abort any running operations like network connections, streams etc. + + $reject(new \Exception('Promise cancelled')); +}; + +$promise = new React\Promise\Promise($resolver, $canceller); +``` + +The promise constructor receives a resolver function and an optional canceller +function which both will be called with 3 arguments: + + * `$resolve($value)` - Primary function that seals the fate of the + returned promise. Accepts either a non-promise value, or another promise. + When called with a non-promise value, fulfills promise with that value. + When called with another promise, e.g. `$resolve($otherPromise)`, promise's + fate will be equivalent to that of `$otherPromise`. + * `$reject($reason)` - Function that rejects the promise. + * `$notify($update)` - Function that issues progress events for the promise. + +If the resolver or canceller throw an exception, the promise will be rejected +with that thrown exception as the rejection reason. + +The resolver function will be called immediately, the canceller function only +once all consumers called the `cancel()` method of the promise. + +### FulfilledPromise + +Creates a already fulfilled promise. + +```php +$promise = React\Promise\FulfilledPromise($value); +``` + +Note, that `$value` **cannot** be a promise. It's recommended to use +[resolve()](#resolve) for creating resolved promises. + +### RejectedPromise + +Creates a already rejected promise. + +```php +$promise = React\Promise\RejectedPromise($reason); +``` + +Note, that `$reason` **cannot** be a promise. It's recommended to use +[reject()](#reject) for creating rejected promises. + +### LazyPromise + +Creates a promise which will be lazily initialized by `$factory` once a consumer +calls the `then()` method. + +```php +$factory = function () { + $deferred = new React\Promise\Deferred(); + + // Do some heavy stuff here and resolve the deferred once completed + + return $deferred->promise(); +}; + +$promise = React\Promise\LazyPromise($factory); + +// $factory will only be executed once we call then() +$promise->then(function ($value) { +}); +``` + +### Functions + +Useful functions for creating, joining, mapping and reducing collections of +promises. + +All functions working on promise collections (like `all()`, `race()`, `some()` +etc.) support cancellation. This means, if you call `cancel()` on the returned +promise, all promises in the collection are cancelled. If the collection itself +is a promise which resolves to an array, this promise is also cancelled. + +#### resolve() + +```php +$promise = React\Promise\resolve(mixed $promiseOrValue); +``` + +Creates a promise for the supplied `$promiseOrValue`. + +If `$promiseOrValue` is a value, it will be the resolution value of the +returned promise. + +If `$promiseOrValue` is a thenable (any object that provides a `then()` method), +a trusted promise that follows the state of the thenable is returned. + +If `$promiseOrValue` is a promise, it will be returned as is. + +Note: The promise returned is always a promise implementing +[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom +promise which only implements [PromiseInterface](#promiseinterface), this +promise will be assimilated to a extended promise following `$promiseOrValue`. + +#### reject() + +```php +$promise = React\Promise\reject(mixed $promiseOrValue); +``` + +Creates a rejected promise for the supplied `$promiseOrValue`. + +If `$promiseOrValue` is a value, it will be the rejection value of the +returned promise. + +If `$promiseOrValue` is a promise, its completion value will be the rejected +value of the returned promise. + +This can be useful in situations where you need to reject a promise without +throwing an exception. For example, it allows you to propagate a rejection with +the value of another promise. + +#### all() + +```php +$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues); +``` + +Returns a promise that will resolve only once all the items in +`$promisesOrValues` have resolved. The resolution value of the returned promise +will be an array containing the resolution values of each of the items in +`$promisesOrValues`. + +#### race() + +```php +$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues); +``` + +Initiates a competitive race that allows one winner. Returns a promise which is +resolved in the same way the first settled promise resolves. + +#### any() + +```php +$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues); +``` + +Returns a promise that will resolve when any one of the items in +`$promisesOrValues` resolves. The resolution value of the returned promise +will be the resolution value of the triggering item. + +The returned promise will only reject if *all* items in `$promisesOrValues` are +rejected. The rejection value will be an array of all rejection reasons. + +The returned promise will also reject with a `React\Promise\Exception\LengthException` +if `$promisesOrValues` contains 0 items. + +#### some() + +```php +$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany); +``` + +Returns a promise that will resolve when `$howMany` of the supplied items in +`$promisesOrValues` resolve. The resolution value of the returned promise +will be an array of length `$howMany` containing the resolution values of the +triggering items. + +The returned promise will reject if it becomes impossible for `$howMany` items +to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items +reject). The rejection value will be an array of +`(count($promisesOrValues) - $howMany) + 1` rejection reasons. + +The returned promise will also reject with a `React\Promise\Exception\LengthException` +if `$promisesOrValues` contains less items than `$howMany`. + +#### map() + +```php +$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc); +``` + +Traditional map function, similar to `array_map()`, but allows input to contain +promises and/or values, and `$mapFunc` may return either a value or a promise. + +The map function receives each item as argument, where item is a fully resolved +value of a promise or value in `$promisesOrValues`. + +#### reduce() + +```php +$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null); +``` + +Traditional reduce function, similar to `array_reduce()`, but input may contain +promises and/or values, and `$reduceFunc` may return either a value or a +promise, *and* `$initialValue` may be a promise or a value for the starting +value. + +### PromisorInterface + +The `React\Promise\PromisorInterface` provides a common interface for objects +that provide a promise. `React\Promise\Deferred` implements it, but since it +is part of the public API anyone can implement it. + +Examples +-------- + +### How to use Deferred + +```php +function getAwesomeResultPromise() +{ + $deferred = new React\Promise\Deferred(); + + // Execute a Node.js-style function using the callback pattern + computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) { + if ($error) { + $deferred->reject($error); + } else { + $deferred->resolve($result); + } + }); + + // Return the promise + return $deferred->promise(); +} + +getAwesomeResultPromise() + ->then( + function ($value) { + // Deferred resolved, do something with $value + }, + function ($reason) { + // Deferred rejected, do something with $reason + }, + function ($update) { + // Progress notification triggered, do something with $update + } + ); +``` + +### How promise forwarding works + +A few simple examples to show how the mechanics of Promises/A forwarding works. +These examples are contrived, of course, and in real usage, promise chains will +typically be spread across several function calls, or even several levels of +your application architecture. + +#### Resolution forwarding + +Resolved promises forward resolution values to the next promise. +The first promise, `$deferred->promise()`, will resolve with the value passed +to `$deferred->resolve()` below. + +Each call to `then()` returns a new promise that will resolve with the return +value of the previous handler. This creates a promise "pipeline". + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + // $x will be the value passed to $deferred->resolve() below + // and returns a *new promise* for $x + 1 + return $x + 1; + }) + ->then(function ($x) { + // $x === 2 + // This handler receives the return value of the + // previous handler. + return $x + 1; + }) + ->then(function ($x) { + // $x === 3 + // This handler receives the return value of the + // previous handler. + return $x + 1; + }) + ->then(function ($x) { + // $x === 4 + // This handler receives the return value of the + // previous handler. + echo 'Resolve ' . $x; + }); + +$deferred->resolve(1); // Prints "Resolve 4" +``` + +#### Rejection forwarding + +Rejected promises behave similarly, and also work similarly to try/catch: +When you catch an exception, you must rethrow for it to propagate. + +Similarly, when you handle a rejected promise, to propagate the rejection, +"rethrow" it by either returning a rejected promise, or actually throwing +(since promise translates thrown exceptions into rejections) + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + throw new \Exception($x + 1); + }) + ->otherwise(function (\Exception $x) { + // Propagate the rejection + throw $x; + }) + ->otherwise(function (\Exception $x) { + // Can also propagate by returning another rejection + return React\Promise\reject( + new \Exception($x->getMessage() + 1) + ); + }) + ->otherwise(function ($x) { + echo 'Reject ' . $x->getMessage(); // 3 + }); + +$deferred->resolve(1); // Prints "Reject 3" +``` + +#### Mixed resolution and rejection forwarding + +Just like try/catch, you can choose to propagate or not. Mixing resolutions and +rejections will still forward handler results in a predictable way. + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + return $x + 1; + }) + ->then(function ($x) { + throw new \Exception($x + 1); + }) + ->otherwise(function (\Exception $x) { + // Handle the rejection, and don't propagate. + // This is like catch without a rethrow + return $x->getMessage() + 1; + }) + ->then(function ($x) { + echo 'Mixed ' . $x; // 4 + }); + +$deferred->resolve(1); // Prints "Mixed 4" +``` + +#### Progress event forwarding + +In the same way as resolution and rejection handlers, your progress handler +**MUST** return a progress event to be propagated to the next link in the chain. +If you return nothing, `null` will be propagated. + +Also in the same way as resolutions and rejections, if you don't register a +progress handler, the update will be propagated through. + +If your progress handler throws an exception, the exception will be propagated +to the next link in the chain. The best thing to do is to ensure your progress +handlers do not throw exceptions. + +This gives you the opportunity to transform progress events at each step in the +chain so that they are meaningful to the next step. It also allows you to choose +not to transform them, and simply let them propagate untransformed, by not +registering a progress handler. + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->progress(function ($update) { + return $update + 1; + }) + ->progress(function ($update) { + echo 'Progress ' . $update; // 2 + }); + +$deferred->notify(1); // Prints "Progress 2" +``` + +### done() vs. then() + +The golden rule is: + + Either return your promise, or call done() on it. + +At a first glance, `then()` and `done()` seem very similar. However, there are +important distinctions. + +The intent of `then()` is to transform a promise's value and to pass or return +a new promise for the transformed value along to other parts of your code. + +The intent of `done()` is to consume a promise's value, transferring +responsibility for the value to your code. + +In addition to transforming a value, `then()` allows you to recover from, or +propagate intermediate errors. Any errors that are not handled will be caught +by the promise machinery and used to reject the promise returned by `then()`. + +Calling `done()` transfers all responsibility for errors to your code. If an +error (either a thrown exception or returned rejection) escapes the +`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be +rethrown in an uncatchable way causing a fatal error. + +```php +function getJsonResult() +{ + return queryApi() + ->then( + // Transform API results to an object + function ($jsonResultString) { + return json_decode($jsonResultString); + }, + // Transform API errors to an exception + function ($jsonErrorString) { + $object = json_decode($jsonErrorString); + throw new ApiErrorException($object->errorMessage); + } + ); +} + +// Here we provide no rejection handler. If the promise returned has been +// rejected, the ApiErrorException will be thrown +getJsonResult() + ->done( + // Consume transformed object + function ($jsonResultObject) { + // Do something with $jsonResultObject + } + ); + +// Here we provide a rejection handler which will either throw while debugging +// or log the exception +getJsonResult() + ->done( + function ($jsonResultObject) { + // Do something with $jsonResultObject + }, + function (ApiErrorException $exception) { + if (isDebug()) { + throw $exception; + } else { + logException($exception); + } + } + ); +``` + +Note that if a rejection value is not an instance of `\Exception`, it will be +wrapped in an exception of the type `React\Promise\UnhandledRejectionException`. + +You can get the original rejection reason by calling `$exception->getReason()`. + +Credits +------- + +React/Promise is a port of [when.js](https://github.com/cujojs/when) +by [Brian Cavalier](https://github.com/briancavalier). + +Also, large parts of the documentation have been ported from the when.js +[Wiki](https://github.com/cujojs/when/wiki) and the +[API docs](https://github.com/cujojs/when/blob/master/docs/api.md). + +License +------- + +React/Promise is released under the [MIT](https://github.com/reactphp/promise/blob/master/LICENSE) license. diff --git a/assets/php/vendor/react/promise/composer.json b/assets/php/vendor/react/promise/composer.json new file mode 100644 index 0000000..2fc4809 --- /dev/null +++ b/assets/php/vendor/react/promise/composer.json @@ -0,0 +1,29 @@ +{ + "name": "react/promise", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "license": "MIT", + "authors": [ + {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"} + ], + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "React\\Promise\\": "tests/fixtures" + } + }, + "keywords": [ + "promise", + "promises" + ] +} diff --git a/assets/php/vendor/react/promise/phpunit.xml.dist b/assets/php/vendor/react/promise/phpunit.xml.dist new file mode 100644 index 0000000..b9a689d --- /dev/null +++ b/assets/php/vendor/react/promise/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + ./tests/ + + + + + + ./src/ + + ./src/functions_include.php + + + + diff --git a/assets/php/vendor/react/promise/src/CancellablePromiseInterface.php b/assets/php/vendor/react/promise/src/CancellablePromiseInterface.php new file mode 100644 index 0000000..896db2d --- /dev/null +++ b/assets/php/vendor/react/promise/src/CancellablePromiseInterface.php @@ -0,0 +1,11 @@ +started) { + return; + } + + $this->started = true; + $this->drain(); + } + + public function enqueue($cancellable) + { + if (!method_exists($cancellable, 'then') || !method_exists($cancellable, 'cancel')) { + return; + } + + $length = array_push($this->queue, $cancellable); + + if ($this->started && 1 === $length) { + $this->drain(); + } + } + + private function drain() + { + for ($i = key($this->queue); isset($this->queue[$i]); $i++) { + $cancellable = $this->queue[$i]; + + $exception = null; + + try { + $cancellable->cancel(); + } catch (\Throwable $exception) { + } catch (\Exception $exception) { + } + + unset($this->queue[$i]); + + if ($exception) { + throw $exception; + } + } + + $this->queue = []; + } +} diff --git a/assets/php/vendor/react/promise/src/Deferred.php b/assets/php/vendor/react/promise/src/Deferred.php new file mode 100644 index 0000000..f23980c --- /dev/null +++ b/assets/php/vendor/react/promise/src/Deferred.php @@ -0,0 +1,60 @@ +canceller = $canceller; + } + + public function promise() + { + if (null === $this->promise) { + $this->promise = new Promise(function ($resolve, $reject, $notify) { + $this->resolveCallback = $resolve; + $this->rejectCallback = $reject; + $this->notifyCallback = $notify; + }, $this->canceller); + } + + return $this->promise; + } + + public function resolve($value = null) + { + $this->promise(); + + call_user_func($this->resolveCallback, $value); + } + + public function reject($reason = null) + { + $this->promise(); + + call_user_func($this->rejectCallback, $reason); + } + + public function notify($update = null) + { + $this->promise(); + + call_user_func($this->notifyCallback, $update); + } + + /** + * @deprecated 2.2.0 + * @see Deferred::notify() + */ + public function progress($update = null) + { + $this->notify($update); + } +} diff --git a/assets/php/vendor/react/promise/src/Exception/LengthException.php b/assets/php/vendor/react/promise/src/Exception/LengthException.php new file mode 100644 index 0000000..775c48d --- /dev/null +++ b/assets/php/vendor/react/promise/src/Exception/LengthException.php @@ -0,0 +1,7 @@ +value = $value; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onFulfilled) { + return $this; + } + + try { + return resolve($onFulfilled($this->value)); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onFulfilled) { + return; + } + + $result = $onFulfilled($this->value); + + if ($result instanceof ExtendedPromiseInterface) { + $result->done(); + } + } + + public function otherwise(callable $onRejected) + { + return $this; + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(function ($value) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }); + } + + public function progress(callable $onProgress) + { + return $this; + } + + public function cancel() + { + } +} diff --git a/assets/php/vendor/react/promise/src/LazyPromise.php b/assets/php/vendor/react/promise/src/LazyPromise.php new file mode 100644 index 0000000..7e3a3d3 --- /dev/null +++ b/assets/php/vendor/react/promise/src/LazyPromise.php @@ -0,0 +1,63 @@ +factory = $factory; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return $this->promise()->then($onFulfilled, $onRejected, $onProgress); + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return $this->promise()->done($onFulfilled, $onRejected, $onProgress); + } + + public function otherwise(callable $onRejected) + { + return $this->promise()->otherwise($onRejected); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->promise()->always($onFulfilledOrRejected); + } + + public function progress(callable $onProgress) + { + return $this->promise()->progress($onProgress); + } + + public function cancel() + { + return $this->promise()->cancel(); + } + + /** + * @internal + * @see Promise::settle() + */ + public function promise() + { + if (null === $this->promise) { + try { + $this->promise = resolve(call_user_func($this->factory)); + } catch (\Throwable $exception) { + $this->promise = new RejectedPromise($exception); + } catch (\Exception $exception) { + $this->promise = new RejectedPromise($exception); + } + } + + return $this->promise; + } +} diff --git a/assets/php/vendor/react/promise/src/Promise.php b/assets/php/vendor/react/promise/src/Promise.php new file mode 100644 index 0000000..0261eb3 --- /dev/null +++ b/assets/php/vendor/react/promise/src/Promise.php @@ -0,0 +1,216 @@ +canceller = $canceller; + $this->call($resolver); + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null !== $this->result) { + return $this->result->then($onFulfilled, $onRejected, $onProgress); + } + + if (null === $this->canceller) { + return new static($this->resolver($onFulfilled, $onRejected, $onProgress)); + } + + $this->requiredCancelRequests++; + + return new static($this->resolver($onFulfilled, $onRejected, $onProgress), function () { + if (++$this->cancelRequests < $this->requiredCancelRequests) { + return; + } + + $this->cancel(); + }); + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null !== $this->result) { + return $this->result->done($onFulfilled, $onRejected, $onProgress); + } + + $this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) { + $promise + ->done($onFulfilled, $onRejected); + }; + + if ($onProgress) { + $this->progressHandlers[] = $onProgress; + } + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, function ($reason) use ($onRejected) { + if (!_checkTypehint($onRejected, $reason)) { + return new RejectedPromise($reason); + } + + return $onRejected($reason); + }); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(function ($value) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }, function ($reason) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($reason) { + return new RejectedPromise($reason); + }); + }); + } + + public function progress(callable $onProgress) + { + return $this->then(null, null, $onProgress); + } + + public function cancel() + { + if (null === $this->canceller || null !== $this->result) { + return; + } + + $canceller = $this->canceller; + $this->canceller = null; + + $this->call($canceller); + } + + private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) { + if ($onProgress) { + $progressHandler = function ($update) use ($notify, $onProgress) { + try { + $notify($onProgress($update)); + } catch (\Throwable $e) { + $notify($e); + } catch (\Exception $e) { + $notify($e); + } + }; + } else { + $progressHandler = $notify; + } + + $this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) { + $promise + ->then($onFulfilled, $onRejected) + ->done($resolve, $reject, $progressHandler); + }; + + $this->progressHandlers[] = $progressHandler; + }; + } + + private function resolve($value = null) + { + if (null !== $this->result) { + return; + } + + $this->settle(resolve($value)); + } + + private function reject($reason = null) + { + if (null !== $this->result) { + return; + } + + $this->settle(reject($reason)); + } + + private function notify($update = null) + { + if (null !== $this->result) { + return; + } + + foreach ($this->progressHandlers as $handler) { + $handler($update); + } + } + + private function settle(ExtendedPromiseInterface $promise) + { + $promise = $this->unwrap($promise); + + $handlers = $this->handlers; + + $this->progressHandlers = $this->handlers = []; + $this->result = $promise; + + foreach ($handlers as $handler) { + $handler($promise); + } + } + + private function unwrap($promise) + { + $promise = $this->extract($promise); + + while ($promise instanceof self && null !== $promise->result) { + $promise = $this->extract($promise->result); + } + + return $promise; + } + + private function extract($promise) + { + if ($promise instanceof LazyPromise) { + $promise = $promise->promise(); + } + + if ($promise === $this) { + return new RejectedPromise( + new \LogicException('Cannot resolve a promise with itself.') + ); + } + + return $promise; + } + + private function call(callable $callback) + { + try { + $callback( + function ($value = null) { + $this->resolve($value); + }, + function ($reason = null) { + $this->reject($reason); + }, + function ($update = null) { + $this->notify($update); + } + ); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } +} diff --git a/assets/php/vendor/react/promise/src/PromiseInterface.php b/assets/php/vendor/react/promise/src/PromiseInterface.php new file mode 100644 index 0000000..d80d114 --- /dev/null +++ b/assets/php/vendor/react/promise/src/PromiseInterface.php @@ -0,0 +1,11 @@ +reason = $reason; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onRejected) { + return $this; + } + + try { + return resolve($onRejected($this->reason)); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onRejected) { + throw UnhandledRejectionException::resolve($this->reason); + } + + $result = $onRejected($this->reason); + + if ($result instanceof self) { + throw UnhandledRejectionException::resolve($result->reason); + } + + if ($result instanceof ExtendedPromiseInterface) { + $result->done(); + } + } + + public function otherwise(callable $onRejected) + { + if (!_checkTypehint($onRejected, $this->reason)) { + return $this; + } + + return $this->then(null, $onRejected); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(null, function ($reason) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($reason) { + return new RejectedPromise($reason); + }); + }); + } + + public function progress(callable $onProgress) + { + return $this; + } + + public function cancel() + { + } +} diff --git a/assets/php/vendor/react/promise/src/UnhandledRejectionException.php b/assets/php/vendor/react/promise/src/UnhandledRejectionException.php new file mode 100644 index 0000000..a44b7a1 --- /dev/null +++ b/assets/php/vendor/react/promise/src/UnhandledRejectionException.php @@ -0,0 +1,31 @@ +reason = $reason; + + $message = sprintf('Unhandled Rejection: %s', json_encode($reason)); + + parent::__construct($message, 0); + } + + public function getReason() + { + return $this->reason; + } +} diff --git a/assets/php/vendor/react/promise/src/functions.php b/assets/php/vendor/react/promise/src/functions.php new file mode 100644 index 0000000..70c0eb7 --- /dev/null +++ b/assets/php/vendor/react/promise/src/functions.php @@ -0,0 +1,244 @@ +then($resolve, $reject, $notify); + }, $canceller); + } + + return new FulfilledPromise($promiseOrValue); +} + +function reject($promiseOrValue = null) +{ + if ($promiseOrValue instanceof PromiseInterface) { + return resolve($promiseOrValue)->then(function ($value) { + return new RejectedPromise($value); + }); + } + + return new RejectedPromise($promiseOrValue); +} + +function all($promisesOrValues) +{ + return map($promisesOrValues, function ($val) { + return $val; + }); +} + +function race($promisesOrValues) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array) || !$array) { + $resolve(); + return; + } + + foreach ($array as $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + + resolve($promiseOrValue) + ->done($resolve, $reject, $notify); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function any($promisesOrValues) +{ + return some($promisesOrValues, 1) + ->then(function ($val) { + return array_shift($val); + }); +} + +function some($promisesOrValues, $howMany) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array) || $howMany < 1) { + $resolve([]); + return; + } + + $len = count($array); + + if ($len < $howMany) { + throw new Exception\LengthException( + sprintf( + 'Input array must contain at least %d item%s but contains only %s item%s.', + $howMany, + 1 === $howMany ? '' : 's', + $len, + 1 === $len ? '' : 's' + ) + ); + } + + $toResolve = $howMany; + $toReject = ($len - $toResolve) + 1; + $values = []; + $reasons = []; + + foreach ($array as $i => $promiseOrValue) { + $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $values[$i] = $val; + + if (0 === --$toResolve) { + $resolve($values); + } + }; + + $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $reasons[$i] = $reason; + + if (0 === --$toReject) { + $reject($reasons); + } + }; + + $cancellationQueue->enqueue($promiseOrValue); + + resolve($promiseOrValue) + ->done($fulfiller, $rejecter, $notify); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function map($promisesOrValues, callable $mapFunc) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array) || !$array) { + $resolve([]); + return; + } + + $toResolve = count($array); + $values = []; + + foreach ($array as $i => $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + $values[$i] = null; + + resolve($promiseOrValue) + ->then($mapFunc) + ->done( + function ($mapped) use ($i, &$values, &$toResolve, $resolve) { + $values[$i] = $mapped; + + if (0 === --$toResolve) { + $resolve($values); + } + }, + $reject, + $notify + ); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array)) { + $array = []; + } + + $total = count($array); + $i = 0; + + // Wrap the supplied $reduceFunc with one that handles promises and then + // delegates to the supplied. + $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) { + $cancellationQueue->enqueue($val); + + return $current + ->then(function ($c) use ($reduceFunc, $total, &$i, $val) { + return resolve($val) + ->then(function ($value) use ($reduceFunc, $total, &$i, $c) { + return $reduceFunc($c, $value, $i++, $total); + }); + }); + }; + + $cancellationQueue->enqueue($initialValue); + + array_reduce($array, $wrappedReduceFunc, resolve($initialValue)) + ->done($resolve, $reject, $notify); + }, $reject, $notify); + }, $cancellationQueue); +} + +// Internal functions +function _checkTypehint(callable $callback, $object) +{ + if (!is_object($object)) { + return true; + } + + if (is_array($callback)) { + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (is_object($callback) && !$callback instanceof \Closure) { + $callbackReflection = new \ReflectionMethod($callback, '__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($callback); + } + + $parameters = $callbackReflection->getParameters(); + + if (!isset($parameters[0])) { + return true; + } + + $expectedException = $parameters[0]; + + if (!$expectedException->getClass()) { + return true; + } + + return $expectedException->getClass()->isInstance($object); +} diff --git a/assets/php/vendor/react/promise/src/functions_include.php b/assets/php/vendor/react/promise/src/functions_include.php new file mode 100644 index 0000000..c71decb --- /dev/null +++ b/assets/php/vendor/react/promise/src/functions_include.php @@ -0,0 +1,5 @@ +enqueue($p); + + $cancellationQueue(); + + $this->assertTrue($p->cancelCalled); + } + + /** @test */ + public function ignoresSimpleCancellable() + { + $p = new SimpleTestCancellable(); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($p); + + $cancellationQueue(); + + $this->assertFalse($p->cancelCalled); + } + + /** @test */ + public function callsCancelOnPromisesEnqueuedBeforeStart() + { + $d1 = $this->getCancellableDeferred(); + $d2 = $this->getCancellableDeferred(); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($d1->promise()); + $cancellationQueue->enqueue($d2->promise()); + + $cancellationQueue(); + } + + /** @test */ + public function callsCancelOnPromisesEnqueuedAfterStart() + { + $d1 = $this->getCancellableDeferred(); + $d2 = $this->getCancellableDeferred(); + + $cancellationQueue = new CancellationQueue(); + + $cancellationQueue(); + + $cancellationQueue->enqueue($d2->promise()); + $cancellationQueue->enqueue($d1->promise()); + } + + /** @test */ + public function doesNotCallCancelTwiceWhenStartedTwice() + { + $d = $this->getCancellableDeferred(); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($d->promise()); + + $cancellationQueue(); + $cancellationQueue(); + } + + /** @test */ + public function rethrowsExceptionsThrownFromCancel() + { + $this->setExpectedException('\Exception', 'test'); + + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel') + ->will($this->throwException(new \Exception('test'))); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($mock); + + $cancellationQueue(); + } + + private function getCancellableDeferred() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return new Deferred($mock); + } +} diff --git a/assets/php/vendor/react/promise/tests/DeferredTest.php b/assets/php/vendor/react/promise/tests/DeferredTest.php new file mode 100644 index 0000000..16212e9 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/DeferredTest.php @@ -0,0 +1,42 @@ + [$d, 'promise'], + 'resolve' => [$d, 'resolve'], + 'reject' => [$d, 'reject'], + 'notify' => [$d, 'progress'], + 'settle' => [$d, 'resolve'], + ]); + } + + /** @test */ + public function progressIsAnAliasForNotify() + { + $deferred = new Deferred(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $deferred->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock); + + $deferred->progress($sentinel); + } +} diff --git a/assets/php/vendor/react/promise/tests/FulfilledPromiseTest.php b/assets/php/vendor/react/promise/tests/FulfilledPromiseTest.php new file mode 100644 index 0000000..97fc8f6 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FulfilledPromiseTest.php @@ -0,0 +1,50 @@ + function () use (&$promise) { + if (!$promise) { + throw new \LogicException('FulfilledPromise must be resolved before obtaining the promise'); + } + + return $promise; + }, + 'resolve' => function ($value = null) use (&$promise) { + if (!$promise) { + $promise = new FulfilledPromise($value); + } + }, + 'reject' => function () { + throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise'); + }, + 'notify' => function () { + // no-op + }, + 'settle' => function ($value = null) use (&$promise) { + if (!$promise) { + $promise = new FulfilledPromise($value); + } + }, + ]); + } + + /** @test */ + public function shouldThrowExceptionIfConstructedWithAPromise() + { + $this->setExpectedException('\InvalidArgumentException'); + + return new FulfilledPromise(new FulfilledPromise()); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionAllTest.php b/assets/php/vendor/react/promise/tests/FunctionAllTest.php new file mode 100644 index 0000000..74c1d7c --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionAllTest.php @@ -0,0 +1,114 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + all([]) + ->then($mock); + } + + /** @test */ + public function shouldResolveValuesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + all([1, 2, 3]) + ->then($mock); + } + + /** @test */ + public function shouldResolvePromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + all([resolve(1), resolve(2), resolve(3)]) + ->then($mock); + } + + /** @test */ + public function shouldResolveSparseArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([null, 1, null, 1, 1])); + + all([null, 1, null, 1, 1]) + ->then($mock); + } + + /** @test */ + public function shouldRejectIfAnyInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + all([resolve(1), reject(2), resolve(3)]) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + all(resolve([1, 2, 3])) + ->then($mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + all(resolve(1)) + ->then($mock); + } + + /** @test */ + public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + $deferred = new Deferred(); + + all([resolve(1), $deferred->promise(), resolve(3)]) + ->then($mock); + + $deferred->resolve(2); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionAnyTest.php b/assets/php/vendor/react/promise/tests/FunctionAnyTest.php new file mode 100644 index 0000000..140b551 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionAnyTest.php @@ -0,0 +1,204 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->callback(function($exception){ + return $exception instanceof LengthException && + 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage(); + }) + ); + + any([]) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveToNullWithNonArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + any(null) + ->then($mock); + } + + /** @test */ + public function shouldResolveWithAnInputValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any([1, 2, 3]) + ->then($mock); + } + + /** @test */ + public function shouldResolveWithAPromisedInputValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any([resolve(1), resolve(2), resolve(3)]) + ->then($mock); + } + + /** @test */ + public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3])); + + any([reject(1), reject(2), reject(3)]) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveWhenFirstInputPromiseResolves() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any([resolve(1), reject(2), reject(3)]) + ->then($mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any(resolve([1, 2, 3])) + ->then($mock); + } + + /** @test */ + public function shouldResolveToNullArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + any(resolve(1)) + ->then($mock); + } + + /** @test */ + public function shouldNotRelyOnArryIndexesWhenUnwrappingToASingleResolutionValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + + any(['abc' => $d1->promise(), 1 => $d2->promise()]) + ->then($mock); + + $d2->resolve(2); + $d1->resolve(1); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + any(reject()) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + any($mock)->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + any([$mock1, $mock2])->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + + $deferred = New Deferred($mock); + $deferred->resolve(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + some([$deferred->promise(), $mock2], 1)->cancel(); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionCheckTypehintTest.php b/assets/php/vendor/react/promise/tests/FunctionCheckTypehintTest.php new file mode 100644 index 0000000..8449bc1 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionCheckTypehintTest.php @@ -0,0 +1,118 @@ +assertTrue(_checkTypehint(function (\InvalidArgumentException $e) { + }, new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint(function (\InvalidArgumentException $e) { + }, new \Exception())); + } + + /** @test */ + public function shouldAcceptFunctionStringCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithTypehint', new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint('React\Promise\testCallbackWithTypehint', new \Exception())); + } + + /** @test */ + public function shouldAcceptInvokableObjectCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception())); + } + + /** @test */ + public function shouldAcceptObjectMethodCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception())); + } + + /** @test */ + public function shouldAcceptStaticClassCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception())); + } + + /** @test */ + public function shouldAcceptClosureCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) { + }, new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptFunctionStringCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithoutTypehint', new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptInvokableObjectCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptObjectMethodCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptStaticClassCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); + } +} + +function testCallbackWithTypehint(\InvalidArgumentException $e) +{ +} + +function testCallbackWithoutTypehint() +{ +} + +class TestCallbackWithTypehintClass +{ + public function __invoke(\InvalidArgumentException $e) + { + + } + + public function testCallback(\InvalidArgumentException $e) + { + + } + + public static function testCallbackStatic(\InvalidArgumentException $e) + { + + } +} + +class TestCallbackWithoutTypehintClass +{ + public function __invoke() + { + + } + + public function testCallback() + { + + } + + public static function testCallbackStatic() + { + + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionMapTest.php b/assets/php/vendor/react/promise/tests/FunctionMapTest.php new file mode 100644 index 0000000..1ea560a --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionMapTest.php @@ -0,0 +1,198 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [1, 2, 3], + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldMapInputPromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [resolve(1), resolve(2), resolve(3)], + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldMapMixedInputArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [1, resolve(2), 3], + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldMapInputWhenMapperReturnsAPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [1, 2, 3], + $this->promiseMapper() + )->then($mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + resolve([1, resolve(2), 3]), + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + map( + resolve(1), + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + $deferred = new Deferred(); + + map( + [resolve(1), $deferred->promise(), resolve(3)], + $this->mapper() + )->then($mock); + + $deferred->resolve(2); + } + + /** @test */ + public function shouldRejectWhenInputContainsRejection() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + map( + [resolve(1), reject(2), resolve(3)], + $this->mapper() + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + map( + reject(), + $this->mapper() + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + map( + $mock, + $this->mapper() + )->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + map( + [$mock1, $mock2], + $this->mapper() + )->cancel(); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionRaceTest.php b/assets/php/vendor/react/promise/tests/FunctionRaceTest.php new file mode 100644 index 0000000..83770ec --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionRaceTest.php @@ -0,0 +1,211 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + [] + )->then($mock); + } + + /** @test */ + public function shouldResolveValuesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + race( + [1, 2, 3] + )->then($mock); + } + + /** @test */ + public function shouldResolvePromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + race( + [$d1->promise(), $d2->promise(), $d3->promise()] + )->then($mock); + + $d2->resolve(2); + + $d1->resolve(1); + $d3->resolve(3); + } + + /** @test */ + public function shouldResolveSparseArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + [null, 1, null, 2, 3] + )->then($mock); + } + + /** @test */ + public function shouldRejectIfFirstSettledPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + race( + [$d1->promise(), $d2->promise(), $d3->promise()] + )->then($this->expectCallableNever(), $mock); + + $d2->reject(2); + + $d1->resolve(1); + $d3->resolve(3); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + race( + resolve([1, 2, 3]) + )->then($mock); + } + + /** @test */ + public function shouldResolveToNullWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + resolve(1) + )->then($mock); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + reject() + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + race($mock)->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + race([$mock1, $mock2])->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->resolve(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + race([$deferred->promise(), $mock2])->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->reject(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + race([$deferred->promise(), $mock2])->cancel(); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionReduceTest.php b/assets/php/vendor/react/promise/tests/FunctionReduceTest.php new file mode 100644 index 0000000..8b43a87 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionReduceTest.php @@ -0,0 +1,347 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(6)); + + reduce( + [1, 2, 3], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldReduceValuesWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [1, 2, 3], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReduceValuesWithInitialPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [1, 2, 3], + $this->plus(), + resolve(1) + )->then($mock); + } + + /** @test */ + public function shouldReducePromisedValuesWithoutInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(6)); + + reduce( + [resolve(1), resolve(2), resolve(3)], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldReducePromisedValuesWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [resolve(1), resolve(2), resolve(3)], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReducePromisedValuesWithInitialPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [resolve(1), resolve(2), resolve(3)], + $this->plus(), + resolve(1) + )->then($mock); + } + + /** @test */ + public function shouldReduceEmptyInputWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + reduce( + [], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReduceEmptyInputWithInitialPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + reduce( + [], + $this->plus(), + resolve(1) + )->then($mock); + } + + /** @test */ + public function shouldRejectWhenInputContainsRejection() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + reduce( + [resolve(1), reject(2), resolve(3)], + $this->plus(), + resolve(1) + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseProvided() + { + // Note: this is different from when.js's behavior! + // In when.reduce(), this rejects with a TypeError exception (following + // JavaScript's [].reduce behavior. + // We're following PHP's array_reduce behavior and resolve with NULL. + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + reduce( + [], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldAllowSparseArrayInputWithoutInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(3)); + + reduce( + [null, null, 1, null, 1, 1], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldAllowSparseArrayInputWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(4)); + + reduce( + [null, null, 1, null, 1, 1], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReduceInInputOrder() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('123')); + + reduce( + [1, 2, 3], + $this->append(), + '' + )->then($mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('123')); + + reduce( + resolve([1, 2, 3]), + $this->append(), + '' + )->then($mock); + } + + /** @test */ + public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + reduce( + resolve(1), + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldProvideCorrectBasisValue() + { + $insertIntoArray = function ($arr, $val, $i) { + $arr[$i] = $val; + + return $arr; + }; + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + reduce( + [$d1->promise(), $d2->promise(), $d3->promise()], + $insertIntoArray, + [] + )->then($mock); + + $d3->resolve(3); + $d1->resolve(1); + $d2->resolve(2); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + reduce( + reject(), + $this->plus(), + 1 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + reduce( + $mock, + $this->plus(), + 1 + )->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + reduce( + [$mock1, $mock2], + $this->plus(), + 1 + )->cancel(); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionRejectTest.php b/assets/php/vendor/react/promise/tests/FunctionRejectTest.php new file mode 100644 index 0000000..84b8ec6 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionRejectTest.php @@ -0,0 +1,64 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + reject($expected) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function shouldRejectAFulfilledPromise() + { + $expected = 123; + + $resolved = new FulfilledPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + reject($resolved) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function shouldRejectARejectedPromise() + { + $expected = 123; + + $resolved = new RejectedPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + reject($resolved) + ->then( + $this->expectCallableNever(), + $mock + ); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionResolveTest.php b/assets/php/vendor/react/promise/tests/FunctionResolveTest.php new file mode 100644 index 0000000..53126bc --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionResolveTest.php @@ -0,0 +1,171 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + resolve($expected) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldResolveAFulfilledPromise() + { + $expected = 123; + + $resolved = new FulfilledPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + resolve($resolved) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldResolveAThenable() + { + $thenable = new SimpleFulfilledTestThenable(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('foo')); + + resolve($thenable) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldResolveACancellableThenable() + { + $thenable = new SimpleTestCancellableThenable(); + + $promise = resolve($thenable); + $promise->cancel(); + + $this->assertTrue($thenable->cancelCalled); + } + + /** @test */ + public function shouldRejectARejectedPromise() + { + $expected = 123; + + $resolved = new RejectedPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + resolve($resolved) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function shouldSupportDeepNestingInPromiseChains() + { + $d = new Deferred(); + $d->resolve(false); + + $result = resolve(resolve($d->promise()->then(function ($val) { + $d = new Deferred(); + $d->resolve($val); + + $identity = function ($val) { + return $val; + }; + + return resolve($d->promise()->then($identity))->then( + function ($val) { + return !$val; + } + ); + }))); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(true)); + + $result->then($mock); + } + + /** @test */ + public function shouldSupportVeryDeepNestedPromises() + { + $deferreds = []; + + // @TODO Increase count once global-queue is merged + for ($i = 0; $i < 10; $i++) { + $deferreds[] = $d = new Deferred(); + $p = $d->promise(); + + $last = $p; + for ($j = 0; $j < 10; $j++) { + $last = $last->then(function($result) { + return $result; + }); + } + } + + $p = null; + foreach ($deferreds as $d) { + if ($p) { + $d->resolve($p); + } + + $p = $d->promise(); + } + + $deferreds[0]->resolve(true); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(true)); + + $deferreds[0]->promise()->then($mock); + } + + /** @test */ + public function returnsExtendePromiseForSimplePromise() + { + $promise = $this + ->getMockBuilder('React\Promise\PromiseInterface') + ->getMock(); + + $this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise)); + } +} diff --git a/assets/php/vendor/react/promise/tests/FunctionSomeTest.php b/assets/php/vendor/react/promise/tests/FunctionSomeTest.php new file mode 100644 index 0000000..276b54b --- /dev/null +++ b/assets/php/vendor/react/promise/tests/FunctionSomeTest.php @@ -0,0 +1,258 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->callback(function($exception){ + return $exception instanceof LengthException && + 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage(); + }) + ); + + some( + [], + 1 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldRejectWithLengthExceptionWithInputArrayContainingNotEnoughItems() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->callback(function($exception){ + return $exception instanceof LengthException && + 'Input array must contain at least 4 items but contains only 3 items.' === $exception->getMessage(); + }) + ); + + some( + [1, 2, 3], + 4 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWithNonArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + some( + null, + 1 + )->then($mock); + } + + /** @test */ + public function shouldResolveValuesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2])); + + some( + [1, 2, 3], + 2 + )->then($mock); + } + + /** @test */ + public function shouldResolvePromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2])); + + some( + [resolve(1), resolve(2), resolve(3)], + 2 + )->then($mock); + } + + /** @test */ + public function shouldResolveSparseArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([null, 1])); + + some( + [null, 1, null, 2, 3], + 2 + )->then($mock); + } + + /** @test */ + public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsAreResolved() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1 => 2, 2 => 3])); + + some( + [resolve(1), reject(2), reject(3)], + 2 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2])); + + some( + resolve([1, 2, 3]), + 2 + )->then($mock); + } + + /** @test */ + public function shouldResolveWithEmptyArrayIfHowManyIsLessThanOne() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + some( + [1], + 0 + )->then($mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + some( + resolve(1), + 1 + )->then($mock); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + some( + reject(), + 1 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + some($mock, 1)->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + some([$mock1, $mock2], 1)->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesFulfill() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->resolve(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + some([$deferred->promise(), $mock2], 1); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesReject() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->reject(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + some([$deferred->promise(), $mock2], 2); + } +} diff --git a/assets/php/vendor/react/promise/tests/LazyPromiseTest.php b/assets/php/vendor/react/promise/tests/LazyPromiseTest.php new file mode 100644 index 0000000..b630881 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/LazyPromiseTest.php @@ -0,0 +1,107 @@ +promise(); + }; + + return new CallbackPromiseAdapter([ + 'promise' => function () use ($factory) { + return new LazyPromise($factory); + }, + 'resolve' => [$d, 'resolve'], + 'reject' => [$d, 'reject'], + 'notify' => [$d, 'progress'], + 'settle' => [$d, 'resolve'], + ]); + } + + /** @test */ + public function shouldNotCallFactoryIfThenIsNotInvoked() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->never()) + ->method('__invoke'); + + new LazyPromise($factory); + } + + /** @test */ + public function shouldCallFactoryIfThenIsInvoked() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke'); + + $p = new LazyPromise($factory); + $p->then(); + } + + /** @test */ + public function shouldReturnPromiseFromFactory() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue(new FulfilledPromise(1))); + + $onFulfilled = $this->createCallableMock(); + $onFulfilled + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $p = new LazyPromise($factory); + + $p->then($onFulfilled); + } + + /** @test */ + public function shouldReturnPromiseIfFactoryReturnsNull() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue(null)); + + $p = new LazyPromise($factory); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then()); + } + + /** @test */ + public function shouldReturnRejectedPromiseIfFactoryThrowsException() + { + $exception = new \Exception(); + + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $onRejected = $this->createCallableMock(); + $onRejected + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $p = new LazyPromise($factory); + + $p->then($this->expectCallableNever(), $onRejected); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php b/assets/php/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php new file mode 100644 index 0000000..bdedf46 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php @@ -0,0 +1,40 @@ +callbacks = $callbacks; + } + + public function promise() + { + return call_user_func_array($this->callbacks['promise'], func_get_args()); + } + + public function resolve() + { + return call_user_func_array($this->callbacks['resolve'], func_get_args()); + } + + public function reject() + { + return call_user_func_array($this->callbacks['reject'], func_get_args()); + } + + public function notify() + { + return call_user_func_array($this->callbacks['notify'], func_get_args()); + } + + public function settle() + { + return call_user_func_array($this->callbacks['settle'], func_get_args()); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php b/assets/php/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php new file mode 100644 index 0000000..9157cd4 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php @@ -0,0 +1,14 @@ + function () use ($promise) { + return $promise; + }, + 'resolve' => $resolveCallback, + 'reject' => $rejectCallback, + 'notify' => $progressCallback, + 'settle' => $resolveCallback, + ]); + } + + /** @test */ + public function shouldRejectIfResolverThrowsException() + { + $exception = new \Exception('foo'); + + $promise = new Promise(function () use ($exception) { + throw $exception; + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $promise + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldFulfillIfFullfilledWithSimplePromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('foo')); + + $adapter->promise() + ->then($mock); + + $adapter->resolve(new SimpleFulfilledTestPromise()); + } + + /** @test */ + public function shouldRejectIfRejectedWithSimplePromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('foo')); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->resolve(new SimpleRejectedTestPromise()); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php new file mode 100644 index 0000000..d722d75 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php @@ -0,0 +1,231 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->isType('callable'), $this->isType('callable'), $this->isType('callable')); + + $adapter = $this->getPromiseTestAdapter($mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldFulfillPromiseIfCancellerFulfills() + { + $adapter = $this->getPromiseTestAdapter(function ($resolve) { + $resolve(1); + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock, $this->expectCallableNever()); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldRejectPromiseIfCancellerRejects() + { + $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) { + $reject(1); + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldRejectPromiseWithExceptionIfCancellerThrows() + { + $e = new \Exception(); + + $adapter = $this->getPromiseTestAdapter(function () use ($e) { + throw $e; + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($e)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldProgressPromiseIfCancellerNotifies() + { + $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $progress) { + $progress(1); + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldCallCancellerOnlyOnceIfCancellerResolves() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnCallback(function ($resolve) { + $resolve(); + })); + + $adapter = $this->getPromiseTestAdapter($mock); + + $adapter->promise()->cancel(); + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldHaveNoEffectIfCancellerDoesNothing() + { + $adapter = $this->getPromiseTestAdapter(function () {}); + + $adapter->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever()); + + $adapter->promise()->cancel(); + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldCallCancellerFromDeepNestedPromiseChain() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + $adapter = $this->getPromiseTestAdapter($mock); + + $promise = $adapter->promise() + ->then(function () { + return new Promise\Promise(function () {}); + }) + ->then(function () { + $d = new Promise\Deferred(); + + return $d->promise(); + }) + ->then(function () { + return new Promise\Promise(function () {}); + }); + + $promise->cancel(); + } + + /** @test */ + public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $child1 = $adapter->promise() + ->then() + ->then(); + + $adapter->promise() + ->then(); + + $child1->cancel(); + } + + /** @test */ + public function cancelShouldTriggerCancellerWhenAllChildrenCancel() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce()); + + $child1 = $adapter->promise() + ->then() + ->then(); + + $child2 = $adapter->promise() + ->then(); + + $child1->cancel(); + $child2->cancel(); + } + + /** @test */ + public function cancelShouldNotTriggerCancellerWhenCancellingOneChildrenMultipleTimes() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $child1 = $adapter->promise() + ->then() + ->then(); + + $child2 = $adapter->promise() + ->then(); + + $child1->cancel(); + $child1->cancel(); + } + + /** @test */ + public function cancelShouldTriggerCancellerOnlyOnceWhenCancellingMultipleTimes() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce()); + + $adapter->promise()->cancel(); + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce()); + + $adapter->promise() + ->then() + ->then(); + + $adapter->promise() + ->then(); + + $adapter->promise()->cancel(); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/FullTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/FullTestTrait.php new file mode 100644 index 0000000..3ce45d6 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/FullTestTrait.php @@ -0,0 +1,15 @@ +getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock); + + $adapter->notify($sentinel); + } + + /** @test */ + public function notifyShouldPropagateProgressToDownstreamPromises() + { + $adapter = $this->getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->notify($sentinel); + } + + /** @test */ + public function notifyShouldPropagateTransformedProgressToDownstreamPromises() + { + $adapter = $this->getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue($sentinel)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldPropagateCaughtExceptionValueAsProgress() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $promise2 = $adapter2->promise(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + // resolve BEFORE attaching progress handler + $adapter->resolve(); + + $adapter->promise() + ->then(function () use ($promise2) { + return $promise2; + }) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ); + + $adapter2->notify($sentinel); + } + + /** @test */ + public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $promise2 = $adapter2->promise(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then(function () use ($promise2) { + return $promise2; + }) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ); + + // resolve AFTER attaching progress handler + $adapter->resolve(); + $adapter2->notify($sentinel); + } + + /** @test */ + public function notifyShouldForwardProgressWhenResolvedWithAnotherPromise() + { + $adapter = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue($sentinel)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->resolve($adapter2->promise()); + $adapter2->notify($sentinel); + } + + /** @test */ + public function notifyShouldAllowResolveAfterProgress() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->at(0)) + ->method('__invoke') + ->with($this->identicalTo(1)); + $mock + ->expects($this->at(1)) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->promise() + ->then( + $mock, + $this->expectCallableNever(), + $mock + ); + + $adapter->notify(1); + $adapter->resolve(2); + } + + /** @test */ + public function notifyShouldAllowRejectAfterProgress() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->at(0)) + ->method('__invoke') + ->with($this->identicalTo(1)); + $mock + ->expects($this->at(1)) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock, + $mock + ); + + $adapter->notify(1); + $adapter->reject(2); + } + + /** @test */ + public function notifyShouldReturnSilentlyOnProgressWhenAlreadyRejected() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(1); + + $this->assertNull($adapter->notify()); + } + + /** @test */ + public function notifyShouldInvokeProgressHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise()->progress($mock); + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldInvokeProgressHandlerFromDone() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $this->assertNull($adapter->promise()->done(null, null, $mock)); + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldThrowExceptionThrownProgressHandlerFromDone() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, null, function () { + throw new \Exception('UnhandledRejectionException'); + })); + $adapter->notify(1); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php new file mode 100644 index 0000000..428230b --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php @@ -0,0 +1,351 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->resolve(1); + $adapter->resolve(2); + + $adapter->promise() + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function fulfilledPromiseShouldInvokeNewlyAddedCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->resolve(1); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock, $this->expectCallableNever()); + } + + /** @test */ + public function thenShouldForwardResultWhenCallbackIsNull() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + null, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldForwardCallbackResultToNextCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + function ($val) { + return $val + 1; + }, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldForwardPromisedCallbackResultValueToNextCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + function ($val) { + return \React\Promise\resolve($val + 1); + }, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + function ($val) { + return \React\Promise\reject($val + 1); + }, + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackThrows() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + $mock, + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock2 + ); + } + + /** @test */ + public function cancelShouldReturnNullForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->resolve(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function cancelShouldHaveNoEffectForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $adapter->resolve(); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function doneShouldInvokeFulfillmentHandlerForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->resolve(1); + $this->assertNull($adapter->promise()->done($mock)); + } + + /** @test */ + public function doneShouldThrowExceptionThrownFulfillmentHandlerForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->resolve(1); + $this->assertNull($adapter->promise()->done(function () { + throw new \Exception('UnhandledRejectionException'); + })); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejectsForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $adapter->resolve(1); + $this->assertNull($adapter->promise()->done(function () { + return \React\Promise\reject(); + })); + } + + /** @test */ + public function otherwiseShouldNotInvokeRejectionHandlerForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->resolve(1); + $adapter->promise()->otherwise($this->expectCallableNever()); + } + + /** @test */ + public function alwaysShouldNotSuppressValueForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->resolve($value); + $adapter->promise() + ->always(function () {}) + ->then($mock); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromiseForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->resolve($value); + $adapter->promise() + ->always(function () { + return 1; + }) + ->then($mock); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromiseForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->resolve($value); + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then($mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->resolve(1); + $adapter->promise() + ->always(function () use ($exception) { + throw $exception; + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->resolve(1); + $adapter->promise() + ->always(function () use ($exception) { + return \React\Promise\reject($exception); + }) + ->then(null, $mock); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php new file mode 100644 index 0000000..a4f48ee --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php @@ -0,0 +1,68 @@ +getPromiseTestAdapter(); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then()); + } + + /** @test */ + public function thenShouldReturnAllowNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null)); + } + + /** @test */ + public function cancelShouldReturnNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function doneShouldReturnNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->done()); + } + + /** @test */ + public function doneShouldReturnAllowNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->done(null, null, null)); + } + + /** @test */ + public function otherwiseShouldNotInvokeRejectionHandlerForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $adapter->promise()->otherwise($this->expectCallableNever()); + } + + /** @test */ + public function alwaysShouldReturnAPromiseForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {})); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php new file mode 100644 index 0000000..98d1dcf --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php @@ -0,0 +1,512 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->reject(1); + $adapter->reject(2); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function rejectedPromiseShouldInvokeNewlyAddedCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(1); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldForwardUndefinedRejectionValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with(null); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function () { + // Presence of rejection handler is enough to switch back + // to resolve mode, even though it returns undefined. + // The ONLY way to propagate a rejection is to re-throw or + // return a rejected promise; + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return $val + 1; + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return \React\Promise\resolve($val + 1); + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldPropagateRejectionsWhenErrbackThrows() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $mock2 + ); + } + + /** @test */ + public function shouldPropagateRejectionsWhenErrbackReturnsARejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return \React\Promise\reject($val + 1); + } + ) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function doneShouldInvokeRejectionHandlerForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, $mock)); + } + + /** @test */ + public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, function () { + throw new \Exception('UnhandledRejectionException'); + })); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done()); + } + + /** @test */ + public function unhandledRejectionExceptionThrownByDoneHoldsRejectionValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $expected = new \stdClass(); + + $adapter->reject($expected); + + try { + $adapter->promise()->done(); + } catch (UnhandledRejectionException $e) { + $this->assertSame($expected, $e->getReason()); + return; + } + + $this->fail(); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(); + })); + } + + /** @test */ + public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(new \Exception('UnhandledRejectionException')); + })); + } + + /** @test */ + public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->reject(new \Exception('UnhandledRejectionException')); + $this->assertNull($adapter->promise()->done()); + } + + /** @test */ + public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise() + { + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $exception = new \Exception('UnhandledRejectionException'); + + $d = new Deferred(); + $d->resolve(); + + $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) { + $d = new Deferred(); + $d->resolve(); + + return \React\Promise\resolve($d->promise()->then(function () {}))->then( + function () use ($exception) { + throw $exception; + } + ); + }))); + + $result->done(); + } + + /** @test */ + public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(new \Exception('UnhandledRejectionException')); + $this->assertNull($adapter->promise()->done(null, function (\Exception $e) { + + })); + } + + /** @test */ + public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->reject(1); + $adapter->promise()->otherwise($mock); + } + + /** @test */ + public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->otherwise(function ($reason) use ($mock) { + $mock($reason); + }); + } + + /** @test */ + public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \InvalidArgumentException(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->otherwise(function (\InvalidArgumentException $reason) use ($mock) { + $mock($reason); + }); + } + + /** @test */ + public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->expectCallableNever(); + + $adapter->reject($exception); + $adapter->promise() + ->otherwise(function (\InvalidArgumentException $reason) use ($mock) { + $mock($reason); + }); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->always(function () {}) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->always(function () { + return 1; + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception1 = new \Exception(); + $exception2 = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception2)); + + $adapter->reject($exception1); + $adapter->promise() + ->always(function () use ($exception2) { + throw $exception2; + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception1 = new \Exception(); + $exception2 = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception2)); + + $adapter->reject($exception1); + $adapter->promise() + ->always(function () use ($exception2) { + return \React\Promise\reject($exception2); + }) + ->then(null, $mock); + } + + /** @test */ + public function cancelShouldReturnNullForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function cancelShouldHaveNoEffectForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $adapter->reject(); + + $adapter->promise()->cancel(); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php new file mode 100644 index 0000000..e363b6d --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php @@ -0,0 +1,86 @@ +getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then()); + } + + /** @test */ + public function thenShouldReturnAllowNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null)); + } + + /** @test */ + public function cancelShouldReturnNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function cancelShouldHaveNoEffectForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $adapter->settle(); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function doneShouldReturnNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertNull($adapter->promise()->done(null, function () {})); + } + + /** @test */ + public function doneShouldReturnAllowNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertNull($adapter->promise()->done(null, function () {}, null)); + } + + /** @test */ + public function progressShouldNotInvokeProgressHandlerForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $adapter->promise()->progress($this->expectCallableNever()); + $adapter->notify(); + } + + /** @test */ + public function alwaysShouldReturnAPromiseForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {})); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php new file mode 100644 index 0000000..063f178 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php @@ -0,0 +1,368 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->reject(1); + } + + /** @test */ + public function rejectShouldRejectWithFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->reject(Promise\resolve(1)); + } + + /** @test */ + public function rejectShouldRejectWithRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->reject(Promise\reject(1)); + } + + /** @test */ + public function rejectShouldForwardReasonWhenCallbackIsNull() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then( + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock + ); + + $adapter->reject(1); + } + + /** @test */ + public function rejectShouldMakePromiseImmutable() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then(null, function ($value) use ($adapter) { + $adapter->reject(3); + + return Promise\reject($value); + }) + ->then( + $this->expectCallableNever(), + $mock + ); + + $adapter->reject(1); + $adapter->reject(2); + } + + /** @test */ + public function notifyShouldInvokeOtherwiseHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->otherwise($mock); + + $adapter->reject(1); + } + + /** @test */ + public function doneShouldInvokeRejectionHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $this->assertNull($adapter->promise()->done(null, $mock)); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowExceptionThrownByRejectionHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, function () { + throw new \Exception('UnhandledRejectionException'); + })); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done()); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(); + })); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(new \Exception('UnhandledRejectionException')); + })); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $d = new Deferred(); + $promise = $d->promise(); + + $this->assertNull($adapter->promise()->done(null, function () use ($promise) { + return $promise; + })); + $adapter->reject(1); + $d->reject(1); + } + + /** @test */ + public function doneShouldThrowExceptionProvidedAsRejectionValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done()); + $adapter->reject(new \Exception('UnhandledRejectionException')); + } + + /** @test */ + public function doneShouldThrowWithDeepNestingPromiseChains() + { + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $exception = new \Exception('UnhandledRejectionException'); + + $d = new Deferred(); + + $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) { + $d = new Deferred(); + $d->resolve(); + + return \React\Promise\resolve($d->promise()->then(function () {}))->then( + function () use ($exception) { + throw $exception; + } + ); + }))); + + $result->done(); + + $d->resolve(); + } + + /** @test */ + public function doneShouldRecoverWhenRejectionHandlerCatchesException() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->done(null, function (\Exception $e) { + + })); + $adapter->reject(new \Exception('UnhandledRejectionException')); + } + + /** @test */ + public function alwaysShouldNotSuppressRejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () {}) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () { + return 1; + }) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForRejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + throw $exception; + }) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForRejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + return \React\Promise\reject($exception); + }) + ->then(null, $mock); + + $adapter->reject($exception); + } +} diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php new file mode 100644 index 0000000..0736d35 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php @@ -0,0 +1,312 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock); + + $adapter->resolve(1); + } + + /** @test */ + public function resolveShouldResolveWithPromisedValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock); + + $adapter->resolve(Promise\resolve(1)); + } + + /** @test */ + public function resolveShouldRejectWhenResolvedWithRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->resolve(Promise\reject(1)); + } + + /** @test */ + public function resolveShouldForwardValueWhenCallbackIsNull() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then( + null, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + + $adapter->resolve(1); + } + + /** @test */ + public function resolveShouldMakePromiseImmutable() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then(function ($value) use ($adapter) { + $adapter->resolve(3); + + return $value; + }) + ->then( + $mock, + $this->expectCallableNever() + ); + + $adapter->resolve(1); + $adapter->resolve(2); + } + + /** + * @test + */ + public function resolveShouldRejectWhenResolvedWithItself() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with(new \LogicException('Cannot resolve a promise with itself.')); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock + ); + + $adapter->resolve($adapter->promise()); + } + + /** + * @test + */ + public function resolveShouldRejectWhenResolvedWithAPromiseWhichFollowsItself() + { + $adapter1 = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with(new \LogicException('Cannot resolve a promise with itself.')); + + $promise1 = $adapter1->promise(); + + $promise2 = $adapter2->promise(); + + $promise2->then( + $this->expectCallableNever(), + $mock + ); + + $adapter1->resolve($promise2); + $adapter2->resolve($promise1); + } + + /** @test */ + public function doneShouldInvokeFulfillmentHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $this->assertNull($adapter->promise()->done($mock)); + $adapter->resolve(1); + } + + /** @test */ + public function doneShouldThrowExceptionThrownFulfillmentHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(function () { + throw new \Exception('UnhandledRejectionException'); + })); + $adapter->resolve(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejects() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(function () { + return \React\Promise\reject(); + })); + $adapter->resolve(1); + } + + /** @test */ + public function alwaysShouldNotSuppressValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->promise() + ->always(function () {}) + ->then($mock); + + $adapter->resolve($value); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->promise() + ->always(function () { + return 1; + }) + ->then($mock); + + $adapter->resolve($value); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then($mock); + + $adapter->resolve($value); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForFulfillment() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + throw $exception; + }) + ->then(null, $mock); + + $adapter->resolve(1); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForFulfillment() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + return \React\Promise\reject($exception); + }) + ->then(null, $mock); + + $adapter->resolve(1); + } +} diff --git a/assets/php/vendor/react/promise/tests/RejectedPromiseTest.php b/assets/php/vendor/react/promise/tests/RejectedPromiseTest.php new file mode 100644 index 0000000..c886b00 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/RejectedPromiseTest.php @@ -0,0 +1,50 @@ + function () use (&$promise) { + if (!$promise) { + throw new \LogicException('RejectedPromise must be rejected before obtaining the promise'); + } + + return $promise; + }, + 'resolve' => function () { + throw new \LogicException('You cannot call resolve() for React\Promise\RejectedPromise'); + }, + 'reject' => function ($reason = null) use (&$promise) { + if (!$promise) { + $promise = new RejectedPromise($reason); + } + }, + 'notify' => function () { + // no-op + }, + 'settle' => function ($reason = null) use (&$promise) { + if (!$promise) { + $promise = new RejectedPromise($reason); + } + }, + ]); + } + + /** @test */ + public function shouldThrowExceptionIfConstructedWithAPromise() + { + $this->setExpectedException('\InvalidArgumentException'); + + return new RejectedPromise(new RejectedPromise()); + } +} diff --git a/assets/php/vendor/react/promise/tests/Stub/CallableStub.php b/assets/php/vendor/react/promise/tests/Stub/CallableStub.php new file mode 100644 index 0000000..0120893 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/Stub/CallableStub.php @@ -0,0 +1,10 @@ +createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + public function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + public function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + public function createCallableMock() + { + return $this + ->getMockBuilder('React\\Promise\Stub\CallableStub') + ->getMock(); + } +} diff --git a/assets/php/vendor/react/promise/tests/bootstrap.php b/assets/php/vendor/react/promise/tests/bootstrap.php new file mode 100644 index 0000000..9b7f872 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/bootstrap.php @@ -0,0 +1,7 @@ +addPsr4('React\\Promise\\', __DIR__); diff --git a/assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php b/assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php new file mode 100644 index 0000000..ef4d530 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php @@ -0,0 +1,21 @@ +cancelCalled = true; + } +} diff --git a/assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php b/assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php new file mode 100644 index 0000000..c0f1593 --- /dev/null +++ b/assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php @@ -0,0 +1,18 @@ +cancelCalled = true; + } +} diff --git a/assets/php/vendor/react/socket/.gitignore b/assets/php/vendor/react/socket/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/assets/php/vendor/react/socket/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/assets/php/vendor/react/socket/.travis.yml b/assets/php/vendor/react/socket/.travis.yml new file mode 100644 index 0000000..917dc0c --- /dev/null +++ b/assets/php/vendor/react/socket/.travis.yml @@ -0,0 +1,49 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 +# - 7.0 # Mac OS X, ignore errors, see below + - hhvm # ignore errors, see below + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + include: + - os: osx + language: generic + php: 7.0 # just to look right on travis + env: + - PACKAGE: php70 + allow_failures: + - php: hhvm + - os: osx + +sudo: false + +install: + # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP + - | + if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then + brew tap homebrew/homebrew-php + echo "Installing PHP ..." + brew install "${PACKAGE}" + brew install "${PACKAGE}"-xdebug + brew link "${PACKAGE}" + echo "Installing composer ..." + curl -s http://getcomposer.org/installer | php + mv composer.phar /usr/local/bin/composer + fi + - composer install --no-interaction + +script: + - ./vendor/bin/phpunit --coverage-text diff --git a/assets/php/vendor/react/socket/CHANGELOG.md b/assets/php/vendor/react/socket/CHANGELOG.md new file mode 100644 index 0000000..03c2eec --- /dev/null +++ b/assets/php/vendor/react/socket/CHANGELOG.md @@ -0,0 +1,451 @@ +# Changelog + +## 0.8.10 (2018-02-28) + +* Feature: Update DNS dependency to support loading system default DNS + nameserver config on all supported platforms + (`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows) + (#152 by @clue) + + This means that connecting to hosts that are managed by a local DNS server, + such as a corporate DNS server or when using Docker containers, will now + work as expected across all platforms with no changes required: + + ```php + $connector = new Connector($loop); + $connector->connect('intranet.example:80')->then(function ($connection) { + // … + }); + ``` + +## 0.8.9 (2018-01-18) + +* Feature: Support explicitly choosing TLS version to negotiate with remote side + by respecting `crypto_method` context parameter for all classes. + (#149 by @clue) + + By default, all connector and server classes support TLSv1.0+ and exclude + support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly + choose the TLS version you want to negotiate with the remote side: + + ```php + // new: now supports 'crypto_method` context parameter for all classes + $connector = new Connector($loop, array( + 'tls' => array( + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + ) + )); + ``` + +* Minor internal clean up to unify class imports + (#148 by @clue) + +## 0.8.8 (2018-01-06) + +* Improve test suite by adding test group to skip integration tests relying on + internet connection and fix minor documentation typo. + (#146 by @clue and #145 by @cn007b) + +## 0.8.7 (2017-12-24) + +* Fix: Fix closing socket resource before removing from loop + (#141 by @clue) + + This fixes the root cause of an uncaught `Exception` that only manifested + itself after the recent Stream v0.7.4 component update and only if you're + using `ext-event` (`ExtEventLoop`). + +* Improve test suite by testing against PHP 7.2 + (#140 by @carusogabriel) + +## 0.8.6 (2017-11-18) + +* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme + and add advanced `UnixServer` class. + (#120 by @andig) + + ```php + // new: Server now supports "unix://" scheme + $server = new Server('unix:///tmp/server.sock', $loop); + + // new: advanced usage + $server = new UnixServer('/tmp/server.sock', $loop); + ``` + +* Restructure examples to ease getting started + (#136 by @clue) + +* Improve test suite by adding forward compatibility with PHPUnit 6 and + ignore Mac OS X test failures for now until Travis tests work again + (#133 by @gabriel-caruso and #134 by @clue) + +## 0.8.5 (2017-10-23) + +* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X + (#123 by @andig) + +* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed + (#129 by @clue) + +* Improve test suite by adding forward compatibility with PHPUnit v5 and + forward compatibility with upcoming EventLoop releases in tests and + test Mac OS X on Travis + (#122 by @andig and #125, #127 and #130 by @clue) + +* Readme improvements + (#118 by @jsor) + +## 0.8.4 (2017-09-16) + +* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead + (#117 by @clue) + + This can be useful for consumers that do not support certain URIs, such as + when you want to explicitly connect to a Unix domain socket (UDS) path + instead of connecting to a default address assumed by an higher-level API: + + ```php + $connector = new FixedUriConnector( + 'unix:///var/run/docker.sock', + new UnixConnector($loop) + ); + + // destination will be ignored, actually connects to Unix domain socket + $promise = $connector->connect('localhost:80'); + ``` + +## 0.8.3 (2017-09-08) + +* Feature: Reduce memory consumption for failed connections + (#113 by @valga) + +* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14 + (#114 by @clue) + +## 0.8.2 (2017-08-25) + +* Feature: Update DNS dependency to support hosts file on all platforms + (#112 by @clue) + + This means that connecting to hosts such as `localhost` will now work as + expected across all platforms with no changes required: + + ```php + $connector = new Connector($loop); + $connector->connect('localhost:8080')->then(function ($connection) { + // … + }); + ``` + +## 0.8.1 (2017-08-15) + +* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and + target evenement 3.0 a long side 2.0 and 1.0 + (#104 by @clue and #111 by @WyriHaximus) + +* Improve test suite by locking Travis distro so new defaults will not break the build and + fix HHVM build for now again and ignore future HHVM build errors + (#109 and #110 by @clue) + +* Minor documentation fixes + (#103 by @christiaan and #108 by @hansott) + +## 0.8.0 (2017-05-09) + +* Feature: New `Server` class now acts as a facade for existing server classes + and renamed old `Server` to `TcpServer` for advanced usage. + (#96 and #97 by @clue) + + The `Server` class is now the main class in this package that implements the + `ServerInterface` and allows you to accept incoming streaming connections, + such as plaintext TCP/IP or secure TLS connection streams. + + > This is not a BC break and consumer code does not have to be updated. + +* Feature / BC break: All addresses are now URIs that include the URI scheme + (#98 by @clue) + + ```diff + - $parts = parse_url('tcp://' . $conn->getRemoteAddress()); + + $parts = parse_url($conn->getRemoteAddress()); + ``` + +* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths + (#100 by @clue) + +* Feature: Forward compatibility with Stream v1.0 and v0.7 + (#99 by @clue) + +## 0.7.2 (2017-04-24) + +* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs + (#94 by @clue) + +## 0.7.1 (2017-04-10) + +* Fix: Ignore HHVM errors when closing connection that is already closing + (#91 by @clue) + +## 0.7.0 (2017-04-10) + +* Feature: Merge SocketClient component into this component + (#87 by @clue) + + This means that this package now provides async, streaming plaintext TCP/IP + and secure TLS socket server and client connections for ReactPHP. + + ``` + $connector = new React\Socket\Connector($loop); + $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) { + $connection->write('…'); + }); + ``` + + Accordingly, the `ConnectionInterface` is now used to represent both incoming + server side connections as well as outgoing client side connections. + + If you've previously used the SocketClient component to establish outgoing + client connections, upgrading should take no longer than a few minutes. + All classes have been merged as-is from the latest `v0.7.0` release with no + other changes, so you can simply update your code to use the updated namespace + like this: + + ```php + // old from SocketClient component and namespace + $connector = new React\SocketClient\Connector($loop); + $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) { + $connection->write('…'); + }); + + // new + $connector = new React\Socket\Connector($loop); + $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) { + $connection->write('…'); + }); + ``` + +## 0.6.0 (2017-04-04) + +* Feature: Add `LimitingServer` to limit and keep track of open connections + (#86 by @clue) + + ```php + $server = new Server(0, $loop); + $server = new LimitingServer($server, 100); + + $server->on('connection', function (ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … + }); + ``` + +* Feature / BC break: Add `pause()` and `resume()` methods to limit active + connections + (#84 by @clue) + + ```php + $server = new Server(0, $loop); + $server->pause(); + + $loop->addTimer(1.0, function() use ($server) { + $server->resume(); + }); + ``` + +## 0.5.1 (2017-03-09) + +* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6 + (#79 by @clue) + +## 0.5.0 (2017-02-14) + +* Feature / BC break: Replace `listen()` call with URIs passed to constructor + and reject listening on hostnames with `InvalidArgumentException` + and replace `ConnectionException` with `RuntimeException` for consistency + (#61, #66 and #72 by @clue) + + ```php + // old + $server = new Server($loop); + $server->listen(8080); + + // new + $server = new Server(8080, $loop); + ``` + + Similarly, you can now pass a full listening URI to the constructor to change + the listening host: + + ```php + // old + $server = new Server($loop); + $server->listen(8080, '127.0.0.1'); + + // new + $server = new Server('127.0.0.1:8080', $loop); + ``` + + Trying to start listening on (DNS) host names will now throw an + `InvalidArgumentException`, use IP addresses instead: + + ```php + // old + $server = new Server($loop); + $server->listen(8080, 'localhost'); + + // new + $server = new Server('127.0.0.1:8080', $loop); + ``` + + If trying to listen fails (such as if port is already in use or port below + 1024 may require root access etc.), it will now throw a `RuntimeException`, + the `ConnectionException` class has been removed: + + ```php + // old: throws React\Socket\ConnectionException + $server = new Server($loop); + $server->listen(80); + + // new: throws RuntimeException + $server = new Server(80, $loop); + ``` + +* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React + (#62 by @clue) + + ```php + // old + $server->shutdown(); + + // new + $server->close(); + ``` + +* Feature / BC break: Replace `getPort()` with `getAddress()` + (#67 by @clue) + + ```php + // old + echo $server->getPort(); // 8080 + + // new + echo $server->getAddress(); // 127.0.0.1:8080 + ``` + +* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP + (#65 by @clue) + + ```php + // old + echo $connection->getRemoteAddress(); // 192.168.0.1 + + // new + echo $connection->getRemoteAddress(); // 192.168.0.1:51743 + ``` + +* Feature / BC break: Add `getLocalAddress()` method + (#68 by @clue) + + ```php + echo $connection->getLocalAddress(); // 127.0.0.1:8080 + ``` + +* BC break: The `Server` and `SecureServer` class are now marked `final` + and you can no longer `extend` them + (which was never documented or recommended anyway). + Public properties and event handlers are now internal only. + Please use composition instead of extension. + (#71, #70 and #69 by @clue) + +## 0.4.6 (2017-01-26) + +* Feature: Support socket context options passed to `Server` + (#64 by @clue) + +* Fix: Properly return `null` for unknown addresses + (#63 by @clue) + +* Improve documentation for `ServerInterface` and lock test suite requirements + (#60 by @clue, #57 by @shaunbramley) + +## 0.4.5 (2017-01-08) + +* Feature: Add `SecureServer` for secure TLS connections + (#55 by @clue) + +* Add functional integration tests + (#54 by @clue) + +## 0.4.4 (2016-12-19) + +* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation + (#50 by @clue) + +* Feature / Fix: Improve test suite and switch to normal stream handler + (#51 by @clue) + +* Feature: Add examples + (#49 by @clue) + +## 0.4.3 (2016-03-01) + +* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing +* Support for PHP7 and HHVM +* Support PHP 5.3 again + +## 0.4.2 (2014-05-25) + +* Verify stream is a valid resource in Connection + +## 0.4.1 (2014-04-13) + +* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev) +* Bug fix: v0.3.4 changes merged for v0.4.1 + +## 0.3.4 (2014-03-30) + +* Bug fix: Reset socket to non-blocking after shutting down (PHP bug) + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to React/Promise 2.0 +* BC break: Update to Evenement 2.0 +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 +* Bump React dependencies to v0.4 + +## 0.3.3 (2013-07-08) + +* Version bump + +## 0.3.2 (2013-05-10) + +* Version bump + +## 0.3.1 (2013-04-21) + +* Feature: Support binding to IPv6 addresses (@clue) + +## 0.3.0 (2013-04-14) + +* Bump React dependencies to v0.3 + +## 0.2.6 (2012-12-26) + +* Version bump + +## 0.2.3 (2012-11-14) + +* Version bump + +## 0.2.0 (2012-09-10) + +* Bump React dependencies to v0.2 + +## 0.1.1 (2012-07-12) + +* Version bump + +## 0.1.0 (2012-07-11) + +* First tagged release diff --git a/assets/php/vendor/react/socket/LICENSE b/assets/php/vendor/react/socket/LICENSE new file mode 100644 index 0000000..a808108 --- /dev/null +++ b/assets/php/vendor/react/socket/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Igor Wiedler, Chris Boden + +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/assets/php/vendor/react/socket/README.md b/assets/php/vendor/react/socket/README.md new file mode 100644 index 0000000..e8b53a0 --- /dev/null +++ b/assets/php/vendor/react/socket/README.md @@ -0,0 +1,1419 @@ +# Socket + +[![Build Status](https://travis-ci.org/reactphp/socket.svg?branch=master)](https://travis-ci.org/reactphp/socket) + +Async, streaming plaintext TCP/IP and secure TLS socket server and client +connections for [ReactPHP](https://reactphp.org/). + +The socket library provides re-usable interfaces for a socket-layer +server and client based on the [`EventLoop`](https://github.com/reactphp/event-loop) +and [`Stream`](https://github.com/reactphp/stream) components. +Its server component allows you to build networking servers that accept incoming +connections from networking clients (such as an HTTP server). +Its client component allows you to build networking clients that establish +outgoing connections to networking servers (such as an HTTP or database client). +This library provides async, streaming means for all of this, so you can +handle multiple concurrent connections without blocking. + +**Table of Contents** + +* [Quickstart example](#quickstart-example) +* [Connection usage](#connection-usage) + * [ConnectionInterface](#connectioninterface) + * [getRemoteAddress()](#getremoteaddress) + * [getLocalAddress()](#getlocaladdress) +* [Server usage](#server-usage) + * [ServerInterface](#serverinterface) + * [connection event](#connection-event) + * [error event](#error-event) + * [getAddress()](#getaddress) + * [pause()](#pause) + * [resume()](#resume) + * [close()](#close) + * [Server](#server) + * [Advanced server usage](#advanced-server-usage) + * [TcpServer](#tcpserver) + * [SecureServer](#secureserver) + * [UnixServer](#unixserver) + * [LimitingServer](#limitingserver) + * [getConnections()](#getconnections) +* [Client usage](#client-usage) + * [ConnectorInterface](#connectorinterface) + * [connect()](#connect) + * [Connector](#connector) + * [Advanced client usage](#advanced-client-usage) + * [TcpConnector](#tcpconnector) + * [DnsConnector](#dnsconnector) + * [SecureConnector](#secureconnector) + * [TimeoutConnector](#timeoutconnector) + * [UnixConnector](#unixconnector) + * [FixUriConnector](#fixeduriconnector) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Quickstart example + +Here is a server that closes the connection if you send it anything: + +```php +$loop = React\EventLoop\Factory::create(); +$socket = new React\Socket\Server('127.0.0.1:8080', $loop); + +$socket->on('connection', function (ConnectionInterface $conn) { + $conn->write("Hello " . $conn->getRemoteAddress() . "!\n"); + $conn->write("Welcome to this amazing server!\n"); + $conn->write("Here's a tip: don't say anything.\n"); + + $conn->on('data', function ($data) use ($conn) { + $conn->close(); + }); +}); + +$loop->run(); +``` + +See also the [examples](examples). + +Here's a client that outputs the output of said server and then attempts to +send it a string: + +```php +$loop = React\EventLoop\Factory::create(); +$connector = new React\Socket\Connector($loop); + +$connector->connect('127.0.0.1:8080')->then(function (ConnectionInterface $conn) use ($loop) { + $conn->pipe(new React\Stream\WritableResourceStream(STDOUT, $loop)); + $conn->write("Hello World!\n"); +}); + +$loop->run(); +``` + +## Connection usage + +### ConnectionInterface + +The `ConnectionInterface` is used to represent any incoming and outgoing +connection, such as a normal TCP/IP connection. + +An incoming or outgoing connection is a duplex stream (both readable and +writable) that implements React's +[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). +It contains additional properties for the local and remote address (client IP) +where this connection has been established to/from. + +Most commonly, instances implementing this `ConnectionInterface` are emitted +by all classes implementing the [`ServerInterface`](#serverinterface) and +used by all classes implementing the [`ConnectorInterface`](#connectorinterface). + +Because the `ConnectionInterface` implements the underlying +[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface) +you can use any of its events and methods as usual: + +```php +$connection->on('data', function ($chunk) { + echo $chunk; +}); + +$connection->on('end', function () { + echo 'ended'; +}); + +$connection->on('error', function (Exception $e) { + echo 'error: ' . $e->getMessage(); +}); + +$connection->on('close', function () { + echo 'closed'; +}); + +$connection->write($data); +$connection->end($data = null); +$connection->close(); +// … +``` + +For more details, see the +[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). + +#### getRemoteAddress() + +The `getRemoteAddress(): ?string` method returns the full remote address +(URI) where this connection has been established with. + +```php +$address = $connection->getRemoteAddress(); +echo 'Connection with ' . $address . PHP_EOL; +``` + +If the remote address can not be determined or is unknown at this time (such as +after the connection has been closed), it MAY return a `NULL` value instead. + +Otherwise, it will return the full address (URI) as a string value, such +as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, +`unix://example.sock` or `unix:///path/to/example.sock`. +Note that individual URI components are application specific and depend +on the underlying transport protocol. + +If this is a TCP/IP based connection and you only want the remote IP, you may +use something like this: + +```php +$address = $connection->getRemoteAddress(); +$ip = trim(parse_url($address, PHP_URL_HOST), '[]'); +echo 'Connection with ' . $ip . PHP_EOL; +``` + +#### getLocalAddress() + +The `getLocalAddress(): ?string` method returns the full local address +(URI) where this connection has been established with. + +```php +$address = $connection->getLocalAddress(); +echo 'Connection with ' . $address . PHP_EOL; +``` + +If the local address can not be determined or is unknown at this time (such as +after the connection has been closed), it MAY return a `NULL` value instead. + +Otherwise, it will return the full address (URI) as a string value, such +as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, +`unix://example.sock` or `unix:///path/to/example.sock`. +Note that individual URI components are application specific and depend +on the underlying transport protocol. + +This method complements the [`getRemoteAddress()`](#getremoteaddress) method, +so they should not be confused. + +If your `TcpServer` instance is listening on multiple interfaces (e.g. using +the address `0.0.0.0`), you can use this method to find out which interface +actually accepted this connection (such as a public or local interface). + +If your system has multiple interfaces (e.g. a WAN and a LAN interface), +you can use this method to find out which interface was actually +used for this connection. + +## Server usage + +### ServerInterface + +The `ServerInterface` is responsible for providing an interface for accepting +incoming streaming connections, such as a normal TCP/IP connection. + +Most higher-level components (such as a HTTP server) accept an instance +implementing this interface to accept incoming streaming connections. +This is usually done via dependency injection, so it's fairly simple to actually +swap this implementation against any other implementation of this interface. +This means that you SHOULD typehint against this interface instead of a concrete +implementation of this interface. + +Besides defining a few methods, this interface also implements the +[`EventEmitterInterface`](https://github.com/igorw/evenement) +which allows you to react to certain events. + +#### connection event + +The `connection` event will be emitted whenever a new connection has been +established, i.e. a new client connects to this server socket: + +```php +$server->on('connection', function (ConnectionInterface $connection) { + echo 'new connection' . PHP_EOL; +}); +``` + +See also the [`ConnectionInterface`](#connectioninterface) for more details +about handling the incoming connection. + +#### error event + +The `error` event will be emitted whenever there's an error accepting a new +connection from a client. + +```php +$server->on('error', function (Exception $e) { + echo 'error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +Note that this is not a fatal error event, i.e. the server keeps listening for +new connections even after this event. + + +#### getAddress() + +The `getAddress(): ?string` method can be used to +return the full address (URI) this server is currently listening on. + +```php +$address = $server->getAddress(); +echo 'Server listening on ' . $address . PHP_EOL; +``` + +If the address can not be determined or is unknown at this time (such as +after the socket has been closed), it MAY return a `NULL` value instead. + +Otherwise, it will return the full address (URI) as a string value, such +as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443` +`unix://example.sock` or `unix:///path/to/example.sock`. +Note that individual URI components are application specific and depend +on the underlying transport protocol. + +If this is a TCP/IP based server and you only want the local port, you may +use something like this: + +```php +$address = $server->getAddress(); +$port = parse_url($address, PHP_URL_PORT); +echo 'Server listening on port ' . $port . PHP_EOL; +``` + +#### pause() + +The `pause(): void` method can be used to +pause accepting new incoming connections. + +Removes the socket resource from the EventLoop and thus stop accepting +new connections. Note that the listening socket stays active and is not +closed. + +This means that new incoming connections will stay pending in the +operating system backlog until its configurable backlog is filled. +Once the backlog is filled, the operating system may reject further +incoming connections until the backlog is drained again by resuming +to accept new connections. + +Once the server is paused, no futher `connection` events SHOULD +be emitted. + +```php +$server->pause(); + +$server->on('connection', assertShouldNeverCalled()); +``` + +This method is advisory-only, though generally not recommended, the +server MAY continue emitting `connection` events. + +Unless otherwise noted, a successfully opened server SHOULD NOT start +in paused state. + +You can continue processing events by calling `resume()` again. + +Note that both methods can be called any number of times, in particular +calling `pause()` more than once SHOULD NOT have any effect. +Similarly, calling this after `close()` is a NO-OP. + +#### resume() + +The `resume(): void` method can be used to +resume accepting new incoming connections. + +Re-attach the socket resource to the EventLoop after a previous `pause()`. + +```php +$server->pause(); + +$loop->addTimer(1.0, function () use ($server) { + $server->resume(); +}); +``` + +Note that both methods can be called any number of times, in particular +calling `resume()` without a prior `pause()` SHOULD NOT have any effect. +Similarly, calling this after `close()` is a NO-OP. + +#### close() + +The `close(): void` method can be used to +shut down this listening socket. + +This will stop listening for new incoming connections on this socket. + +```php +echo 'Shutting down server socket' . PHP_EOL; +$server->close(); +``` + +Calling this method more than once on the same instance is a NO-OP. + +### Server + +The `Server` class is the main class in this package that implements the +[`ServerInterface`](#serverinterface) and allows you to accept incoming +streaming connections, such as plaintext TCP/IP or secure TLS connection streams. +Connections can also be accepted on Unix domain sockets. + +```php +$server = new Server(8080, $loop); +``` + +As above, the `$uri` parameter can consist of only a port, in which case the +server will default to listening on the localhost address `127.0.0.1`, +which means it will not be reachable from outside of this system. + +In order to use a random port assignment, you can use the port `0`: + +```php +$server = new Server(0, $loop); +$address = $server->getAddress(); +``` + +In order to change the host the socket is listening on, you can provide an IP +address through the first parameter provided to the constructor, optionally +preceded by the `tcp://` scheme: + +```php +$server = new Server('192.168.0.1:8080', $loop); +``` + +If you want to listen on an IPv6 address, you MUST enclose the host in square +brackets: + +```php +$server = new Server('[::1]:8080', $loop); +``` + +To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the +`unix://` scheme: + +```php +$server = new Server('unix:///tmp/server.sock', $loop); +``` + +If the given URI is invalid, does not contain a port, any other scheme or if it +contains a hostname, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException due to missing port +$server = new Server('127.0.0.1', $loop); +``` + +If the given URI appears to be valid, but listening on it fails (such as if port +is already in use or port below 1024 may require root access etc.), it will +throw a `RuntimeException`: + +```php +$first = new Server(8080, $loop); + +// throws RuntimeException because port is already in use +$second = new Server(8080, $loop); +``` + +> Note that these error conditions may vary depending on your system and/or + configuration. + See the exception message and code for more details about the actual error + condition. + +Optionally, you can specify [TCP socket context options](http://php.net/manual/en/context.socket.php) +for the underlying stream socket resource like this: + +```php +$server = new Server('[::1]:8080', $loop, array( + 'tcp' => array( + 'backlog' => 200, + 'so_reuseport' => true, + 'ipv6_v6only' => true + ) +)); +``` + +> Note that available [socket context options](http://php.net/manual/en/context.socket.php), + their defaults and effects of changing these may vary depending on your system + and/or PHP version. + Passing unknown context options has no effect. + For BC reasons, you can also pass the TCP socket context options as a simple + array without wrapping this in another array under the `tcp` key. + +You can start a secure TLS (formerly known as SSL) server by simply prepending +the `tls://` URI scheme. +Internally, it will wait for plaintext TCP/IP connections and then performs a +TLS handshake for each connection. +It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php), +which in its most basic form may look something like this if you're using a +PEM encoded certificate file: + +```php +$server = new Server('tls://127.0.0.1:8080', $loop, array( + 'tls' => array( + 'local_cert' => 'server.pem' + ) +)); +``` + +> Note that the certificate file will not be loaded on instantiation but when an + incoming connection initializes its TLS context. + This implies that any invalid certificate file paths or contents will only cause + an `error` event at a later time. + +If your private key is encrypted with a passphrase, you have to specify it +like this: + +```php +$server = new Server('tls://127.0.0.1:8000', $loop, array( + 'tls' => array( + 'local_cert' => 'server.pem', + 'passphrase' => 'secret' + ) +)); +``` + +By default, this server supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$server = new Server('tls://127.0.0.1:8000', $loop, array( + 'tls' => array( + 'local_cert' => 'server.pem', + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER + ) +)); +``` + +> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php), + their defaults and effects of changing these may vary depending on your system + and/or PHP version. + The outer context array allows you to also use `tcp` (and possibly more) + context options at the same time. + Passing unknown context options has no effect. + If you do not use the `tls://` scheme, then passing `tls` context options + has no effect. + +Whenever a client connects, it will emit a `connection` event with a connection +instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$server->on('connection', function (ConnectionInterface $connection) { + echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +> Note that the `Server` class is a concrete implementation for TCP/IP sockets. + If you want to typehint in your higher-level protocol implementation, you SHOULD + use the generic [`ServerInterface`](#serverinterface) instead. + +### Advanced server usage + +#### TcpServer + +The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and +is responsible for accepting plaintext TCP/IP connections. + +```php +$server = new TcpServer(8080, $loop); +``` + +As above, the `$uri` parameter can consist of only a port, in which case the +server will default to listening on the localhost address `127.0.0.1`, +which means it will not be reachable from outside of this system. + +In order to use a random port assignment, you can use the port `0`: + +```php +$server = new TcpServer(0, $loop); +$address = $server->getAddress(); +``` + +In order to change the host the socket is listening on, you can provide an IP +address through the first parameter provided to the constructor, optionally +preceded by the `tcp://` scheme: + +```php +$server = new TcpServer('192.168.0.1:8080', $loop); +``` + +If you want to listen on an IPv6 address, you MUST enclose the host in square +brackets: + +```php +$server = new TcpServer('[::1]:8080', $loop); +``` + +If the given URI is invalid, does not contain a port, any other scheme or if it +contains a hostname, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException due to missing port +$server = new TcpServer('127.0.0.1', $loop); +``` + +If the given URI appears to be valid, but listening on it fails (such as if port +is already in use or port below 1024 may require root access etc.), it will +throw a `RuntimeException`: + +```php +$first = new TcpServer(8080, $loop); + +// throws RuntimeException because port is already in use +$second = new TcpServer(8080, $loop); +``` + +> Note that these error conditions may vary depending on your system and/or +configuration. +See the exception message and code for more details about the actual error +condition. + +Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php) +for the underlying stream socket resource like this: + +```php +$server = new TcpServer('[::1]:8080', $loop, array( + 'backlog' => 200, + 'so_reuseport' => true, + 'ipv6_v6only' => true +)); +``` + +> Note that available [socket context options](http://php.net/manual/en/context.socket.php), +their defaults and effects of changing these may vary depending on your system +and/or PHP version. +Passing unknown context options has no effect. + +Whenever a client connects, it will emit a `connection` event with a connection +instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$server->on('connection', function (ConnectionInterface $connection) { + echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +#### SecureServer + +The `SecureServer` class implements the [`ServerInterface`](#serverinterface) +and is responsible for providing a secure TLS (formerly known as SSL) server. + +It does so by wrapping a [`TcpServer`](#tcpserver) instance which waits for plaintext +TCP/IP connections and then performs a TLS handshake for each connection. +It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php), +which in its most basic form may look something like this if you're using a +PEM encoded certificate file: + +```php +$server = new TcpServer(8000, $loop); +$server = new SecureServer($server, $loop, array( + 'local_cert' => 'server.pem' +)); +``` + +> Note that the certificate file will not be loaded on instantiation but when an +incoming connection initializes its TLS context. +This implies that any invalid certificate file paths or contents will only cause +an `error` event at a later time. + +If your private key is encrypted with a passphrase, you have to specify it +like this: + +```php +$server = new TcpServer(8000, $loop); +$server = new SecureServer($server, $loop, array( + 'local_cert' => 'server.pem', + 'passphrase' => 'secret' +)); +``` + +By default, this server supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$server = new TcpServer(8000, $loop); +$server = new SecureServer($server, $loop, array( + 'local_cert' => 'server.pem', + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER +)); +``` + +> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php), +their defaults and effects of changing these may vary depending on your system +and/or PHP version. +Passing unknown context options has no effect. + +Whenever a client completes the TLS handshake, it will emit a `connection` event +with a connection instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$server->on('connection', function (ConnectionInterface $connection) { + echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +Whenever a client fails to perform a successful TLS handshake, it will emit an +`error` event and then close the underlying TCP/IP connection: + +```php +$server->on('error', function (Exception $e) { + echo 'Error' . $e->getMessage() . PHP_EOL; +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +Note that the `SecureServer` class is a concrete implementation for TLS sockets. +If you want to typehint in your higher-level protocol implementation, you SHOULD +use the generic [`ServerInterface`](#serverinterface) instead. + +> Advanced usage: Despite allowing any `ServerInterface` as first parameter, +you SHOULD pass a `TcpServer` instance as first parameter, unless you +know what you're doing. +Internally, the `SecureServer` has to set the required TLS context options on +the underlying stream resources. +These resources are not exposed through any of the interfaces defined in this +package, but only through the internal `Connection` class. +The `TcpServer` class is guaranteed to emit connections that implement +the `ConnectionInterface` and uses the internal `Connection` class in order to +expose these underlying resources. +If you use a custom `ServerInterface` and its `connection` event does not +meet this requirement, the `SecureServer` will emit an `error` event and +then close the underlying connection. + +#### UnixServer + +The `UnixServer` class implements the [`ServerInterface`](#serverinterface) and +is responsible for accepting connections on Unix domain sockets (UDS). + +```php +$server = new UnixServer('/tmp/server.sock', $loop); +``` + +As above, the `$uri` parameter can consist of only a socket path or socket path +prefixed by the `unix://` scheme. + +If the given URI appears to be valid, but listening on it fails (such as if the +socket is already in use or the file not accessible etc.), it will throw a +`RuntimeException`: + +```php +$first = new UnixServer('/tmp/same.sock', $loop); + +// throws RuntimeException because socket is already in use +$second = new UnixServer('/tmp/same.sock', $loop); +``` + +Whenever a client connects, it will emit a `connection` event with a connection +instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$server->on('connection', function (ConnectionInterface $connection) { + echo 'New connection' . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +#### LimitingServer + +The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible +for limiting and keeping track of open connections to this server instance. + +Whenever the underlying server emits a `connection` event, it will check its +limits and then either + - keep track of this connection by adding it to the list of + open connections and then forward the `connection` event + - or reject (close) the connection when its limits are exceeded and will + forward an `error` event instead. + +Whenever a connection closes, it will remove this connection from the list of +open connections. + +```php +$server = new LimitingServer($server, 100); +$server->on('connection', function (ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [second example](examples) for more details. + +You have to pass a maximum number of open connections to ensure +the server will automatically reject (close) connections once this limit +is exceeded. In this case, it will emit an `error` event to inform about +this and no `connection` event will be emitted. + +```php +$server = new LimitingServer($server, 100); +$server->on('connection', function (ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +You MAY pass a `null` limit in order to put no limit on the number of +open connections and keep accepting new connection until you run out of +operating system resources (such as open file handles). This may be +useful if you do not want to take care of applying a limit but still want +to use the `getConnections()` method. + +You can optionally configure the server to pause accepting new +connections once the connection limit is reached. In this case, it will +pause the underlying server and no longer process any new connections at +all, thus also no longer closing any excessive connections. +The underlying operating system is responsible for keeping a backlog of +pending connections until its limit is reached, at which point it will +start rejecting further connections. +Once the server is below the connection limit, it will continue consuming +connections from the backlog and will process any outstanding data on +each connection. +This mode may be useful for some protocols that are designed to wait for +a response message (such as HTTP), but may be less useful for other +protocols that demand immediate responses (such as a "welcome" message in +an interactive chat). + +```php +$server = new LimitingServer($server, 100, true); +$server->on('connection', function (ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +##### getConnections() + +The `getConnections(): ConnectionInterface[]` method can be used to +return an array with all currently active connections. + +```php +foreach ($server->getConnection() as $connection) { + $connection->write('Hi!'); +} +``` + +## Client usage + +### ConnectorInterface + +The `ConnectorInterface` is responsible for providing an interface for +establishing streaming connections, such as a normal TCP/IP connection. + +This is the main interface defined in this package and it is used throughout +React's vast ecosystem. + +Most higher-level components (such as HTTP, database or other networking +service clients) accept an instance implementing this interface to create their +TCP/IP connection to the underlying networking service. +This is usually done via dependency injection, so it's fairly simple to actually +swap this implementation against any other implementation of this interface. + +The interface only offers a single method: + +#### connect() + +The `connect(string $uri): PromiseInterface` method +can be used to create a streaming connection to the given remote address. + +It returns a [Promise](https://github.com/reactphp/promise) which either +fulfills with a stream implementing [`ConnectionInterface`](#connectioninterface) +on success or rejects with an `Exception` if the connection is not successful: + +```php +$connector->connect('google.com:443')->then( + function (ConnectionInterface $connection) { + // connection successfully established + }, + function (Exception $error) { + // failed to connect due to $error + } +); +``` + +See also [`ConnectionInterface`](#connectioninterface) for more details. + +The returned Promise MUST be implemented in such a way that it can be +cancelled when it is still pending. Cancelling a pending promise MUST +reject its value with an `Exception`. It SHOULD clean up any underlying +resources and references as applicable: + +```php +$promise = $connector->connect($uri); + +$promise->cancel(); +``` + +### Connector + +The `Connector` class is the main class in this package that implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create streaming connections. + +You can use this connector to create any kind of streaming connections, such +as plaintext TCP/IP, secure TLS or local Unix connection streams. + +It binds to the main event loop and can be used like this: + +```php +$loop = React\EventLoop\Factory::create(); +$connector = new Connector($loop); + +$connector->connect($uri)->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); + +$loop->run(); +``` + +In order to create a plaintext TCP/IP connection, you can simply pass a host +and port combination like this: + +```php +$connector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +> If you do no specify a URI scheme in the destination URI, it will assume + `tcp://` as a default and establish a plaintext TCP/IP connection. + Note that TCP/IP connections require a host and port part in the destination + URI like above, all other URI components are optional. + +In order to create a secure TLS connection, you can use the `tls://` URI scheme +like this: + +```php +$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +In order to create a local Unix domain socket connection, you can use the +`unix://` URI scheme like this: + +```php +$connector->connect('unix:///tmp/demo.sock')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +> The [`getRemoteAddress()`](#getremoteaddress) method will return the target + Unix domain socket (UDS) path as given to the `connect()` method, including + the `unix://` scheme, for example `unix:///tmp/demo.sock`. + The [`getLocalAddress()`](#getlocaladdress) method will most likely return a + `null` value as this value is not applicable to UDS connections here. + +Under the hood, the `Connector` is implemented as a *higher-level facade* +for the lower-level connectors implemented in this package. This means it +also shares all of their features and implementation details. +If you want to typehint in your higher-level protocol implementation, you SHOULD +use the generic [`ConnectorInterface`](#connectorinterface) instead. + +The `Connector` class will try to detect your system DNS settings (and uses +Google's public DNS server `8.8.8.8` as a fallback if unable to determine your +system settings) to resolve all public hostnames into underlying IP addresses by +default. +If you explicitly want to use a custom DNS server (such as a local DNS relay or +a company wide DNS server), you can set up the `Connector` like this: + +```php +$connector = new Connector($loop, array( + 'dns' => '127.0.1.1' +)); + +$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +If you do not want to use a DNS resolver at all and want to connect to IP +addresses only, you can also set up your `Connector` like this: + +```php +$connector = new Connector($loop, array( + 'dns' => false +)); + +$connector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +Advanced: If you need a custom DNS `Resolver` instance, you can also set up +your `Connector` like this: + +```php +$dnsResolverFactory = new React\Dns\Resolver\Factory(); +$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop); + +$connector = new Connector($loop, array( + 'dns' => $resolver +)); + +$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +By default, the `tcp://` and `tls://` URI schemes will use timeout value that +repects your `default_socket_timeout` ini setting (which defaults to 60s). +If you want a custom timeout value, you can simply pass this like this: + +```php +$connector = new Connector($loop, array( + 'timeout' => 10.0 +)); +``` + +Similarly, if you do not want to apply a timeout at all and let the operating +system handle this, you can pass a boolean flag like this: + +```php +$connector = new Connector($loop, array( + 'timeout' => false +)); +``` + +By default, the `Connector` supports the `tcp://`, `tls://` and `unix://` +URI schemes. If you want to explicitly prohibit any of these, you can simply +pass boolean flags like this: + +```php +// only allow secure TLS connections +$connector = new Connector($loop, array( + 'tcp' => false, + 'tls' => true, + 'unix' => false, +)); + +$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +The `tcp://` and `tls://` also accept additional context options passed to +the underlying connectors. +If you want to explicitly pass additional context options, you can simply +pass arrays of context options like this: + +```php +// allow insecure TLS connections +$connector = new Connector($loop, array( + 'tcp' => array( + 'bindto' => '192.168.0.1:0' + ), + 'tls' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + ), +)); + +$connector->connect('tls://localhost:443')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +By default, this connector supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$connector = new Connector($loop, array( + 'tls' => array( + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + ) +)); +``` + +> For more details about context options, please refer to the PHP documentation + about [socket context options](http://php.net/manual/en/context.socket.php) + and [SSL context options](http://php.net/manual/en/context.ssl.php). + +Advanced: By default, the `Connector` supports the `tcp://`, `tls://` and +`unix://` URI schemes. +For this, it sets up the required connector classes automatically. +If you want to explicitly pass custom connectors for any of these, you can simply +pass an instance implementing the `ConnectorInterface` like this: + +```php +$dnsResolverFactory = new React\Dns\Resolver\Factory(); +$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop); +$tcp = new DnsConnector(new TcpConnector($loop), $resolver); + +$tls = new SecureConnector($tcp, $loop); + +$unix = new UnixConnector($loop); + +$connector = new Connector($loop, array( + 'tcp' => $tcp, + 'tls' => $tls, + 'unix' => $unix, + + 'dns' => false, + 'timeout' => false, +)); + +$connector->connect('google.com:80')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +> Internally, the `tcp://` connector will always be wrapped by the DNS resolver, + unless you disable DNS like in the above example. In this case, the `tcp://` + connector receives the actual hostname instead of only the resolved IP address + and is thus responsible for performing the lookup. + Internally, the automatically created `tls://` connector will always wrap the + underlying `tcp://` connector for establishing the underlying plaintext + TCP/IP connection before enabling secure TLS mode. If you want to use a custom + underlying `tcp://` connector for secure TLS connections only, you may + explicitly pass a `tls://` connector like above instead. + Internally, the `tcp://` and `tls://` connectors will always be wrapped by + `TimeoutConnector`, unless you disable timeouts like in the above example. + +### Advanced client usage + +#### TcpConnector + +The `React\Socket\TcpConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext +TCP/IP connections to any IP-port-combination: + +```php +$tcpConnector = new React\Socket\TcpConnector($loop); + +$tcpConnector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); + +$loop->run(); +``` + +See also the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $tcpConnector->connect('127.0.0.1:80'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will close the underlying socket +resource, thus cancelling the pending TCP/IP connection, and reject the +resulting promise. + +You can optionally pass additional +[socket context options](http://php.net/manual/en/context.socket.php) +to the constructor like this: + +```php +$tcpConnector = new React\Socket\TcpConnector($loop, array( + 'bindto' => '192.168.0.1:0' +)); +``` + +Note that this class only allows you to connect to IP-port-combinations. +If the given URI is invalid, does not contain a valid IP address and port +or contains any other scheme, it will reject with an +`InvalidArgumentException`: + +If the given URI appears to be valid, but connecting to it fails (such as if +the remote host rejects the connection etc.), it will reject with a +`RuntimeException`. + +If you want to connect to hostname-port-combinations, see also the following chapter. + +> Advanced usage: Internally, the `TcpConnector` allocates an empty *context* +resource for each stream resource. +If the destination URI contains a `hostname` query parameter, its value will +be used to set up the TLS peer name. +This is used by the `SecureConnector` and `DnsConnector` to verify the peer +name and can also be used if you want a custom TLS peer name. + +#### DnsConnector + +The `DnsConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext +TCP/IP connections to any hostname-port-combination. + +It does so by decorating a given `TcpConnector` instance so that it first +looks up the given domain name via DNS (if applicable) and then establishes the +underlying TCP/IP connection to the resolved target IP address. + +Make sure to set up your DNS resolver and underlying TCP connector like this: + +```php +$dnsResolverFactory = new React\Dns\Resolver\Factory(); +$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop); + +$dnsConnector = new React\Socket\DnsConnector($tcpConnector, $dns); + +$dnsConnector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); + +$loop->run(); +``` + +See also the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $dnsConnector->connect('www.google.com:80'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will cancel the underlying DNS lookup +and/or the underlying TCP/IP connection and reject the resulting promise. + +> Advanced usage: Internally, the `DnsConnector` relies on a `Resolver` to +look up the IP address for the given hostname. +It will then replace the hostname in the destination URI with this IP and +append a `hostname` query parameter and pass this updated URI to the underlying +connector. +The underlying connector is thus responsible for creating a connection to the +target IP address, while this query parameter can be used to check the original +hostname and is used by the `TcpConnector` to set up the TLS peer name. +If a `hostname` is given explicitly, this query parameter will not be modified, +which can be useful if you want a custom TLS peer name. + +#### SecureConnector + +The `SecureConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create secure +TLS (formerly known as SSL) connections to any hostname-port-combination. + +It does so by decorating a given `DnsConnector` instance so that it first +creates a plaintext TCP/IP connection and then enables TLS encryption on this +stream. + +```php +$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop); + +$secureConnector->connect('www.google.com:443')->then(function (ConnectionInterface $connection) { + $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); + ... +}); + +$loop->run(); +``` + +See also the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $secureConnector->connect('www.google.com:443'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will cancel the underlying TCP/IP +connection and/or the SSL/TLS negotiation and reject the resulting promise. + +You can optionally pass additional +[SSL context options](http://php.net/manual/en/context.ssl.php) +to the constructor like this: + +```php +$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array( + 'verify_peer' => false, + 'verify_peer_name' => false +)); +``` + +By default, this connector supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array( + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT +)); +``` + +> Advanced usage: Internally, the `SecureConnector` relies on setting up the +required *context options* on the underlying stream resource. +It should therefor be used with a `TcpConnector` somewhere in the connector +stack so that it can allocate an empty *context* resource for each stream +resource and verify the peer name. +Failing to do so may result in a TLS peer name mismatch error or some hard to +trace race conditions, because all stream resources will use a single, shared +*default context* resource otherwise. + +#### TimeoutConnector + +The `TimeoutConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to add timeout +handling to any existing connector instance. + +It does so by decorating any given [`ConnectorInterface`](#connectorinterface) +instance and starting a timer that will automatically reject and abort any +underlying connection attempt if it takes too long. + +```php +$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0, $loop); + +$timeoutConnector->connect('google.com:80')->then(function (ConnectionInterface $connection) { + // connection succeeded within 3.0 seconds +}); +``` + +See also any of the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $timeoutConnector->connect('google.com:80'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will cancel the underlying connection +attempt, abort the timer and reject the resulting promise. + +#### UnixConnector + +The `UnixConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to connect to +Unix domain socket (UDS) paths like this: + +```php +$connector = new React\Socket\UnixConnector($loop); + +$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) { + $connection->write("HELLO\n"); +}); + +$loop->run(); +``` + +Connecting to Unix domain sockets is an atomic operation, i.e. its promise will +settle (either resolve or reject) immediately. +As such, calling `cancel()` on the resulting promise has no effect. + +> The [`getRemoteAddress()`](#getremoteaddress) method will return the target + Unix domain socket (UDS) path as given to the `connect()` method, prepended + with the `unix://` scheme, for example `unix:///tmp/demo.sock`. + The [`getLocalAddress()`](#getlocaladdress) method will most likely return a + `null` value as this value is not applicable to UDS connections here. + +#### FixedUriConnector + +The `FixedUriConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and decorates an existing Connector +to always use a fixed, preconfigured URI. + +This can be useful for consumers that do not support certain URIs, such as +when you want to explicitly connect to a Unix domain socket (UDS) path +instead of connecting to a default address assumed by an higher-level API: + +```php +$connector = new FixedUriConnector( + 'unix:///var/run/docker.sock', + new UnixConnector($loop) +); + +// destination will be ignored, actually connects to Unix domain socket +$promise = $connector->connect('localhost:80'); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require react/socket:^0.8.10 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM. +It's *highly recommended to use PHP 7+* for this project, partly due to its vast +performance improvements and partly because legacy PHP versions require several +workarounds as described below. + +Secure TLS connections received some major upgrades starting with PHP 5.6, with +the defaults now being more secure, while older versions required explicit +context options. +This library does not take responsibility over these context options, so it's +up to consumers of this library to take care of setting appropriate context +options as described above. + +All versions of PHP prior to 5.6.8 suffered from a buffering issue where reading +from a streaming TLS connection could be one `data` event behind. +This library implements a work-around to try to flush the complete incoming +data buffers on these legacy PHP versions, which has a penalty of around 10% of +throughput on all connections. +With this work-around, we have not been able to reproduce this issue anymore, +but we have seen reports of people saying this could still affect some of the +older PHP versions (`5.5.23`, `5.6.7`, and `5.6.8`). +Note that this only affects *some* higher-level streaming protocols, such as +IRC over TLS, but should not affect HTTP over TLS (HTTPS). +Further investigation of this issue is needed. +For more insights, this issue is also covered by our test suite. + +PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big +chunks of data over TLS streams at once. +We try to work around this by limiting the write chunk size to 8192 +bytes for older PHP versions only. +This is only a work-around and has a noticable performance penalty on +affected versions. + +This project also supports running on HHVM. +Note that really old HHVM < 3.8 does not support secure TLS connections, as it +lacks the required `stream_socket_enable_crypto()` function. +As such, trying to create a secure TLS connections on affected versions will +return a rejected promise instead. +This issue is also covered by our test suite, which will skip related tests +on affected versions. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +$ php vendor/bin/phpunit --exclude-group internet +``` + +## License + +MIT, see [LICENSE file](LICENSE). diff --git a/assets/php/vendor/react/socket/composer.json b/assets/php/vendor/react/socket/composer.json new file mode 100644 index 0000000..bc85aab --- /dev/null +++ b/assets/php/vendor/react/socket/composer.json @@ -0,0 +1,29 @@ +{ + "name": "react/socket", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": ["async", "socket", "stream", "connection", "ReactPHP"], + "license": "MIT", + "require": { + "php": ">=5.3.0", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "react/dns": "^0.4.13", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/stream": "^1.0 || ^0.7.1", + "react/promise": "^2.1 || ^1.2", + "react/promise-timer": "~1.0" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "autoload": { + "psr-4": { + "React\\Socket\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\Socket\\": "tests" + } + } +} diff --git a/assets/php/vendor/react/socket/examples/01-echo-server.php b/assets/php/vendor/react/socket/examples/01-echo-server.php new file mode 100644 index 0000000..2c0be57 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/01-echo-server.php @@ -0,0 +1,42 @@ + array( + 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem') + ) +)); + +$server->on('connection', function (ConnectionInterface $conn) { + echo '[' . $conn->getRemoteAddress() . ' connected]' . PHP_EOL; + $conn->pipe($conn); +}); + +$server->on('error', 'printf'); + +echo 'Listening on ' . $server->getAddress() . PHP_EOL; + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/02-chat-server.php b/assets/php/vendor/react/socket/examples/02-chat-server.php new file mode 100644 index 0000000..46439e0 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/02-chat-server.php @@ -0,0 +1,59 @@ + array( + 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem') + ) +)); + +$server = new LimitingServer($server, null); + +$server->on('connection', function (ConnectionInterface $client) use ($server) { + // whenever a new message comes in + $client->on('data', function ($data) use ($client, $server) { + // remove any non-word characters (just for the demo) + $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data)); + + // ignore empty messages + if ($data === '') { + return; + } + + // prefix with client IP and broadcast to all connected clients + $data = trim(parse_url($client->getRemoteAddress(), PHP_URL_HOST), '[]') . ': ' . $data . PHP_EOL; + foreach ($server->getConnections() as $connection) { + $connection->write($data); + } + }); +}); + +$server->on('error', 'printf'); + +echo 'Listening on ' . $server->getAddress() . PHP_EOL; + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/03-http-server.php b/assets/php/vendor/react/socket/examples/03-http-server.php new file mode 100644 index 0000000..eb6d454 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/03-http-server.php @@ -0,0 +1,57 @@ + array( + 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem') + ) +)); + +$server->on('connection', function (ConnectionInterface $conn) { + $conn->once('data', function () use ($conn) { + $body = "

Hello world!

\r\n"; + $conn->end("HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\nConnection: close\r\n\r\n" . $body); + }); +}); + +$server->on('error', 'printf'); + +echo 'Listening on ' . strtr($server->getAddress(), array('tcp:' => 'http:', 'tls:' => 'https:')) . PHP_EOL; + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/11-http-client.php b/assets/php/vendor/react/socket/examples/11-http-client.php new file mode 100644 index 0000000..2b64a43 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/11-http-client.php @@ -0,0 +1,36 @@ +connect($host. ':80')->then(function (ConnectionInterface $connection) use ($host) { + $connection->on('data', function ($data) { + echo $data; + }); + $connection->on('close', function () { + echo '[CLOSED]' . PHP_EOL; + }); + + $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n"); +}, 'printf'); + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/12-https-client.php b/assets/php/vendor/react/socket/examples/12-https-client.php new file mode 100644 index 0000000..6e3f279 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/12-https-client.php @@ -0,0 +1,36 @@ +connect('tls://' . $host . ':443')->then(function (ConnectionInterface $connection) use ($host) { + $connection->on('data', function ($data) { + echo $data; + }); + $connection->on('close', function () { + echo '[CLOSED]' . PHP_EOL; + }); + + $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n"); +}, 'printf'); + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/21-netcat-client.php b/assets/php/vendor/react/socket/examples/21-netcat-client.php new file mode 100644 index 0000000..9140e2c --- /dev/null +++ b/assets/php/vendor/react/socket/examples/21-netcat-client.php @@ -0,0 +1,68 @@ +' . PHP_EOL); + exit(1); +} + +$loop = Factory::create(); +$connector = new Connector($loop); + +$stdin = new ReadableResourceStream(STDIN, $loop); +$stdin->pause(); +$stdout = new WritableResourceStream(STDOUT, $loop); +$stderr = new WritableResourceStream(STDERR, $loop); + +$stderr->write('Connecting' . PHP_EOL); + +$connector->connect($argv[1])->then(function (ConnectionInterface $connection) use ($stdin, $stdout, $stderr) { + // pipe everything from STDIN into connection + $stdin->resume(); + $stdin->pipe($connection); + + // pipe everything from connection to STDOUT + $connection->pipe($stdout); + + // report errors to STDERR + $connection->on('error', function ($error) use ($stderr) { + $stderr->write('Stream ERROR: ' . $error . PHP_EOL); + }); + + // report closing and stop reading from input + $connection->on('close', function () use ($stderr, $stdin) { + $stderr->write('[CLOSED]' . PHP_EOL); + $stdin->close(); + }); + + $stderr->write('Connected' . PHP_EOL); +}, function ($error) use ($stderr) { + $stderr->write('Connection ERROR: ' . $error . PHP_EOL); +}); + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/22-http-client.php b/assets/php/vendor/react/socket/examples/22-http-client.php new file mode 100644 index 0000000..fcb8107 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/22-http-client.php @@ -0,0 +1,60 @@ +' . PHP_EOL); + exit(1); +} + +$loop = Factory::create(); +$connector = new Connector($loop); + +if (!isset($parts['port'])) { + $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80; +} + +$host = $parts['host']; +if (($parts['scheme'] === 'http' && $parts['port'] !== 80) || ($parts['scheme'] === 'https' && $parts['port'] !== 443)) { + $host .= ':' . $parts['port']; +} +$target = ($parts['scheme'] === 'https' ? 'tls' : 'tcp') . '://' . $parts['host'] . ':' . $parts['port']; +$resource = isset($parts['path']) ? $parts['path'] : '/'; +if (isset($parts['query'])) { + $resource .= '?' . $parts['query']; +} + +$stdout = new WritableResourceStream(STDOUT, $loop); + +$connector->connect($target)->then(function (ConnectionInterface $connection) use ($resource, $host, $stdout) { + $connection->pipe($stdout); + + $connection->write("GET $resource HTTP/1.0\r\nHost: $host\r\n\r\n"); +}, 'printf'); + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/91-benchmark-server.php b/assets/php/vendor/react/socket/examples/91-benchmark-server.php new file mode 100644 index 0000000..420d474 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/91-benchmark-server.php @@ -0,0 +1,60 @@ + array( + 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem') + ) +)); + +$server->on('connection', function (ConnectionInterface $conn) use ($loop) { + echo '[connected]' . PHP_EOL; + + // count the number of bytes received from this connection + $bytes = 0; + $conn->on('data', function ($chunk) use (&$bytes) { + $bytes += strlen($chunk); + }); + + // report average throughput once client disconnects + $t = microtime(true); + $conn->on('close', function () use ($conn, $t, &$bytes) { + $t = microtime(true) - $t; + echo '[disconnected after receiving ' . $bytes . ' bytes in ' . round($t, 3) . 's => ' . round($bytes / $t / 1024 / 1024, 1) . ' MiB/s]' . PHP_EOL; + }); +}); + +$server->on('error', 'printf'); + +echo 'Listening on ' . $server->getAddress() . PHP_EOL; + +$loop->run(); diff --git a/assets/php/vendor/react/socket/examples/99-generate-self-signed.php b/assets/php/vendor/react/socket/examples/99-generate-self-signed.php new file mode 100644 index 0000000..00f9314 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/99-generate-self-signed.php @@ -0,0 +1,31 @@ + secret.pem + +// certificate details (Distinguished Name) +// (OpenSSL applies defaults to missing fields) +$dn = array( + "commonName" => isset($argv[1]) ? $argv[1] : "localhost", +// "countryName" => "AU", +// "stateOrProvinceName" => "Some-State", +// "localityName" => "London", +// "organizationName" => "Internet Widgits Pty Ltd", +// "organizationalUnitName" => "R&D", +// "emailAddress" => "admin@example.com" +); + +// create certificate which is valid for ~10 years +$privkey = openssl_pkey_new(); +$cert = openssl_csr_new($dn, $privkey); +$cert = openssl_csr_sign($cert, null, $privkey, 3650); + +// export public and (optionally encrypted) private key in PEM format +openssl_x509_export($cert, $out); +echo $out; + +$passphrase = isset($argv[2]) ? $argv[2] : null; +openssl_pkey_export($privkey, $out, $passphrase); +echo $out; diff --git a/assets/php/vendor/react/socket/examples/localhost.pem b/assets/php/vendor/react/socket/examples/localhost.pem new file mode 100644 index 0000000..be69279 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/localhost.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu +MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK +DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx +MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw +EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py +W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN +2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9 +zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0 +UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4 +wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY +YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf +BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G +CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN +0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3 +7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe +824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3 +V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII +IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7 +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py +W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN +2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9 +zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0 +UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4 +wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY +YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV +ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/ +g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK +tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1 +LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7 +tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk +9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR +43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V +pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om +OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I +2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I +li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH +b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY +vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb +XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I +Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR +iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L ++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv +y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe +81oh1uCH1YPLM29hPyaohxL8 +-----END PRIVATE KEY----- diff --git a/assets/php/vendor/react/socket/examples/localhost_swordfish.pem b/assets/php/vendor/react/socket/examples/localhost_swordfish.pem new file mode 100644 index 0000000..7d1ee80 --- /dev/null +++ b/assets/php/vendor/react/socket/examples/localhost_swordfish.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu +MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK +DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQxMDQzWhcNMjYx +MjI4MTQxMDQzWjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw +EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRXt83SrKIHr/i +3lc8O8pz6NHE1DNHJa4xg2xalXWzCEV6m1qLd9VdaLT9cJD1afNmEMBgY6RblNL/ +paJWVoR9MOUeIoYl2PrhUCxsf7h6MRtezQQe3e+n+/0XunF0JUQIZuJqbxfRk5WT +XmYnphqOZKEcistAYvFBjzl/D+Cl/nYsreADc+t9l5Vni89oTWEuqIrsM4WUZqqB +VMAakd2nZJLWIrMxq9hbW1XNukOQfcmZVFTC6CUnLq8qzGbtfZYBuMBACnL1k/E/ +yPaAgR46l14VAcndDUJBtMeL2qYuNwvXQhg3KuBmpTUpH+yzxU+4T3lmv0xXmPqu +ySH3xvW3AgMBAAGjUDBOMB0GA1UdDgQWBBRu68WTI4pVeTB7wuG9QGI3Ie441TAf +BgNVHSMEGDAWgBRu68WTI4pVeTB7wuG9QGI3Ie441TAMBgNVHRMEBTADAQH/MA0G +CSqGSIb3DQEBBQUAA4IBAQCc4pEjEHO47VRJkbHgC+c2gAVgxekkaA1czBA1uAvh +ILRda0NLlvyftbjaG0zZp2ABUCfRfksl/Pf/PzWLUMEuH/9kEW2rgP43z6YgiL6k +kBPlmAU607UjD726RPGkw8QPSXS/dWiNJ5CBpPWLpxC45pokqItYbY0ijQ5Piq09 +TchYlCX044oSRnPiP394PQ3HVdaGhJB2DnjDq3in5dVivFf8EdgzQSvp/wXy3WQs +uFSVonSnrZGY/4AgT3psGaQ6fqKb4SBoqtf5bFQvp1XNNRkuEJnS/0dygEya0c+c +aCe/1gXC2wDjx0/TekY5m1Nyw5SY6z7stOqL/ekwgejt +-----END CERTIFICATE----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIG7idPRLgiHkCAggA +MBQGCCqGSIb3DQMHBAg+MLPdepHWSwSCBMgVW9LseCjfTAmF9U1qRnKsq3kIwEnW +6aERBqs/mnmEhrXgZYgcvRRK7kD12TdHt/Nz46Ymu0h+Lrvuwtl1fHQUARTk/gFh +onLhc9kjMUhLRIR007vJe3HvWOb/v+SBSDB38OpUxUwJmBVBuSaYLWVuPR6J5kUj +xOgBS049lN3E9cfrHvb3bF/epIQrU0OgfyyxEvIi5n30y+tlRn3y68PY6Qd46t4Y +UN5VZUwvJBgoRy9TGxSkiSRjhxC2PWpLYq/HMzDbcRcFF5dVAIioUd/VZ7fdgBfA +uMW4SFfpFLDUX0aaYe+ZdA5tM0Bc0cOtG8Z0sc9JYDNNcvjmSiGCi646h8F0D3O6 +JKAQMMxQGWiyQeJ979LVjtq4lJESXA8VEKz9rV03y5xunmFCLy6dGt+6GJwXgabn +OH7nvEv4GqAOqKc6E9je4JM+AF/oUazrfPse1KEEtsPKarazjCB/SKYtHyDJaavD +GGjtiU9zWwGMOgIDyNmXe3ga7/TWoGOAg5YlTr6Hbq2Y/5ycgjAgPFjuXtvnoT0+ +mF5TnNfMAqTgQsE2gjhonK1pdlOen0lN5FtoUXp3CXU0dOq0J70GiX+1YA7VDn30 +n5WNAgfOXX3l3E95jGN370pHXyli5RUNW0NZVHV+22jlNWCVtQHUh+DVswQZg+i5 ++DqaIHz2jUetMo7gWtqGn/wwSopOs87VM1rcALhZL4EsJ+Zy81I/hA32RNnGbuol +NAiZh+0KrtTcc/fPunpd8vRtOwGphM11dKucozUufuiPG2inR3aEqt5yNx54ec/f +J6nryWRYiHEA/rCU9MSBM9cqKFtEmy9/8oxV41/SPxhXjHwDlABWTtFuJ3pf2sOF +ILSYYFwB0ZGvdjE5yAJFBr9efno/L9fafmGk7a3vmVgK2AmUC9VNB5XHw1GjF8OP +aQAXe4md9Bh0jk/D/iyp7e7IWNssul/7XejhabidWgFj6EXc9YxE59+FlhDqyMhn +V6houc+QeUXuwsAKgRJJhJtpv/QSZ5BI3esxHHUt3ayGnvhFElpAc0t7C/EiXKIv +DAFYP2jksBqijM8YtEgPWYzEP5buYxZnf/LK7FDocLsNcdF38UaKBbeF90e7bR8j +SHspG9aJWICu8Yawnh8zuy/vQv+h9gWyGodd2p9lQzlbRXrutbwfmPf7xP6nzT9i +9GcugJxTaZgkCfhhHxFk/nRHS2NAzagKVib1xkUlZJg2hX0fIFUdYteL1GGTvOx5 +m3mTOino4T19z9SEdZYb2OHYh29e/T74bJiLCYdXwevSYHxfZc8pYAf0jp4UnMT2 +f7B0ctX1iXuQ2uZVuxh+U1Mcu+v0gDla1jWh7AhcePSi4xBNUCak0kQip6r5e6Oi +r4MIyMRk/Pc5pzEKo8G6nk26rNvX3aRvECoVfmK7IVdsqZ6IXlt9kOmWx3IeKzrO +J5DxpzW+9oIRZJgPTkc4/XRb0tFmFQYTiChiQ1AJUEiCX0GpkFf7cq61aLGYtWyn +vL2lmQhljzjrDo15hKErvk7eBZW7GW/6j/m/PfRdcBI4ceuP9zWQXnDOd9zmaE4b +q3bJ+IbbyVZA2WwyzN7umCKWghsiPMAolxEnYM9JRf8BcqeqQiwVZlfO5KFuN6Ze +le4= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/assets/php/vendor/react/socket/phpunit.xml.dist b/assets/php/vendor/react/socket/phpunit.xml.dist new file mode 100644 index 0000000..13d3fab --- /dev/null +++ b/assets/php/vendor/react/socket/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/assets/php/vendor/react/socket/src/Connection.php b/assets/php/vendor/react/socket/src/Connection.php new file mode 100644 index 0000000..c6267cc --- /dev/null +++ b/assets/php/vendor/react/socket/src/Connection.php @@ -0,0 +1,178 @@ += 70100 && PHP_VERSION_ID < 70104)); + + $this->input = new DuplexResourceStream( + $resource, + $loop, + $clearCompleteBuffer ? -1 : null, + new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null) + ); + + $this->stream = $resource; + + Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain')); + + $this->input->on('close', array($this, 'close')); + } + + public function isReadable() + { + return $this->input->isReadable(); + } + + public function isWritable() + { + return $this->input->isWritable(); + } + + public function pause() + { + $this->input->pause(); + } + + public function resume() + { + $this->input->resume(); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return $this->input->pipe($dest, $options); + } + + public function write($data) + { + return $this->input->write($data); + } + + public function end($data = null) + { + $this->input->end($data); + } + + public function close() + { + $this->input->close(); + $this->handleClose(); + $this->removeAllListeners(); + } + + public function handleClose() + { + if (!is_resource($this->stream)) { + return; + } + + // Try to cleanly shut down socket and ignore any errors in case other + // side already closed. Shutting down may return to blocking mode on + // some legacy versions, so reset to non-blocking just in case before + // continuing to close the socket resource. + // Underlying Stream implementation will take care of closing file + // handle, so we otherwise keep this open here. + @stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR); + stream_set_blocking($this->stream, false); + } + + public function getRemoteAddress() + { + return $this->parseAddress(@stream_socket_get_name($this->stream, true)); + } + + public function getLocalAddress() + { + return $this->parseAddress(@stream_socket_get_name($this->stream, false)); + } + + private function parseAddress($address) + { + if ($address === false) { + return null; + } + + if ($this->unix) { + // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo + // note that technically ":" is a valid address, so keep this in place otherwise + if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) { + $address = (string)substr($address, 0, -1); + } + + // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556 + // PHP uses "\0" string and HHVM uses empty string (colon removed above) + if ($address === '' || $address[0] === "\x00" ) { + return null; + } + + return 'unix://' . $address; + } + + // check if this is an IPv6 address which includes multiple colons but no square brackets + $pos = strrpos($address, ':'); + if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') { + $port = substr($address, $pos + 1); + $address = '[' . substr($address, 0, $pos) . ']:' . $port; + } + + return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address; + } +} diff --git a/assets/php/vendor/react/socket/src/ConnectionInterface.php b/assets/php/vendor/react/socket/src/ConnectionInterface.php new file mode 100644 index 0000000..64613b5 --- /dev/null +++ b/assets/php/vendor/react/socket/src/ConnectionInterface.php @@ -0,0 +1,119 @@ +on('data', function ($chunk) { + * echo $chunk; + * }); + * + * $connection->on('end', function () { + * echo 'ended'; + * }); + * + * $connection->on('error', function (Exception $e) { + * echo 'error: ' . $e->getMessage(); + * }); + * + * $connection->on('close', function () { + * echo 'closed'; + * }); + * + * $connection->write($data); + * $connection->end($data = null); + * $connection->close(); + * // … + * ``` + * + * For more details, see the + * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). + * + * @see DuplexStreamInterface + * @see ServerInterface + * @see ConnectorInterface + */ +interface ConnectionInterface extends DuplexStreamInterface +{ + /** + * Returns the full remote address (URI) where this connection has been established with + * + * ```php + * $address = $connection->getRemoteAddress(); + * echo 'Connection with ' . $address . PHP_EOL; + * ``` + * + * If the remote address can not be determined or is unknown at this time (such as + * after the connection has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, + * `unix://example.sock` or `unix:///path/to/example.sock`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * If this is a TCP/IP based connection and you only want the remote IP, you may + * use something like this: + * + * ```php + * $address = $connection->getRemoteAddress(); + * $ip = trim(parse_url($address, PHP_URL_HOST), '[]'); + * echo 'Connection with ' . $ip . PHP_EOL; + * ``` + * + * @return ?string remote address (URI) or null if unknown + */ + public function getRemoteAddress(); + + /** + * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with + * + * ```php + * $address = $connection->getLocalAddress(); + * echo 'Connection with ' . $address . PHP_EOL; + * ``` + * + * If the local address can not be determined or is unknown at this time (such as + * after the connection has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, + * `unix://example.sock` or `unix:///path/to/example.sock`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * This method complements the [`getRemoteAddress()`](#getremoteaddress) method, + * so they should not be confused. + * + * If your `TcpServer` instance is listening on multiple interfaces (e.g. using + * the address `0.0.0.0`), you can use this method to find out which interface + * actually accepted this connection (such as a public or local interface). + * + * If your system has multiple interfaces (e.g. a WAN and a LAN interface), + * you can use this method to find out which interface was actually + * used for this connection. + * + * @return ?string local address (URI) or null if unknown + * @see self::getRemoteAddress() + */ + public function getLocalAddress(); +} diff --git a/assets/php/vendor/react/socket/src/Connector.php b/assets/php/vendor/react/socket/src/Connector.php new file mode 100644 index 0000000..75276bc --- /dev/null +++ b/assets/php/vendor/react/socket/src/Connector.php @@ -0,0 +1,136 @@ + true, + 'tls' => true, + 'unix' => true, + + 'dns' => true, + 'timeout' => true, + ); + + if ($options['timeout'] === true) { + $options['timeout'] = (float)ini_get("default_socket_timeout"); + } + + if ($options['tcp'] instanceof ConnectorInterface) { + $tcp = $options['tcp']; + } else { + $tcp = new TcpConnector( + $loop, + is_array($options['tcp']) ? $options['tcp'] : array() + ); + } + + if ($options['dns'] !== false) { + if ($options['dns'] instanceof Resolver) { + $resolver = $options['dns']; + } else { + if ($options['dns'] !== true) { + $server = $options['dns']; + } else { + // try to load nameservers from system config or default to Google's public DNS + $config = Config::loadSystemConfigBlocking(); + $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + } + + $factory = new Factory(); + $resolver = $factory->create( + $server, + $loop + ); + } + + $tcp = new DnsConnector($tcp, $resolver); + } + + if ($options['tcp'] !== false) { + $options['tcp'] = $tcp; + + if ($options['timeout'] !== false) { + $options['tcp'] = new TimeoutConnector( + $options['tcp'], + $options['timeout'], + $loop + ); + } + + $this->connectors['tcp'] = $options['tcp']; + } + + if ($options['tls'] !== false) { + if (!$options['tls'] instanceof ConnectorInterface) { + $options['tls'] = new SecureConnector( + $tcp, + $loop, + is_array($options['tls']) ? $options['tls'] : array() + ); + } + + if ($options['timeout'] !== false) { + $options['tls'] = new TimeoutConnector( + $options['tls'], + $options['timeout'], + $loop + ); + } + + $this->connectors['tls'] = $options['tls']; + } + + if ($options['unix'] !== false) { + if (!$options['unix'] instanceof ConnectorInterface) { + $options['unix'] = new UnixConnector($loop); + } + $this->connectors['unix'] = $options['unix']; + } + } + + public function connect($uri) + { + $scheme = 'tcp'; + if (strpos($uri, '://') !== false) { + $scheme = (string)substr($uri, 0, strpos($uri, '://')); + } + + if (!isset($this->connectors[$scheme])) { + return Promise\reject(new RuntimeException( + 'No connector available for URI scheme "' . $scheme . '"' + )); + } + + return $this->connectors[$scheme]->connect($uri); + } +} + diff --git a/assets/php/vendor/react/socket/src/ConnectorInterface.php b/assets/php/vendor/react/socket/src/ConnectorInterface.php new file mode 100644 index 0000000..196d01a --- /dev/null +++ b/assets/php/vendor/react/socket/src/ConnectorInterface.php @@ -0,0 +1,58 @@ +connect('google.com:443')->then( + * function (ConnectionInterface $connection) { + * // connection successfully established + * }, + * function (Exception $error) { + * // failed to connect due to $error + * } + * ); + * ``` + * + * The returned Promise MUST be implemented in such a way that it can be + * cancelled when it is still pending. Cancelling a pending promise MUST + * reject its value with an Exception. It SHOULD clean up any underlying + * resources and references as applicable. + * + * ```php + * $promise = $connector->connect($uri); + * + * $promise->cancel(); + * ``` + * + * @param string $uri + * @return \React\Promise\PromiseInterface resolves with a stream implementing ConnectionInterface on success or rejects with an Exception on error + * @see ConnectionInterface + */ + public function connect($uri); +} diff --git a/assets/php/vendor/react/socket/src/DnsConnector.php b/assets/php/vendor/react/socket/src/DnsConnector.php new file mode 100644 index 0000000..90170e5 --- /dev/null +++ b/assets/php/vendor/react/socket/src/DnsConnector.php @@ -0,0 +1,111 @@ +connector = $connector; + $this->resolver = $resolver; + } + + public function connect($uri) + { + if (strpos($uri, '://') === false) { + $parts = parse_url('tcp://' . $uri); + unset($parts['scheme']); + } else { + $parts = parse_url($uri); + } + + if (!$parts || !isset($parts['host'])) { + return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid')); + } + + $host = trim($parts['host'], '[]'); + $connector = $this->connector; + + return $this + ->resolveHostname($host) + ->then(function ($ip) use ($connector, $host, $parts) { + $uri = ''; + + // prepend original scheme if known + if (isset($parts['scheme'])) { + $uri .= $parts['scheme'] . '://'; + } + + if (strpos($ip, ':') !== false) { + // enclose IPv6 addresses in square brackets before appending port + $uri .= '[' . $ip . ']'; + } else { + $uri .= $ip; + } + + // append original port if known + if (isset($parts['port'])) { + $uri .= ':' . $parts['port']; + } + + // append orignal path if known + if (isset($parts['path'])) { + $uri .= $parts['path']; + } + + // append original query if known + if (isset($parts['query'])) { + $uri .= '?' . $parts['query']; + } + + // append original hostname as query if resolved via DNS and if + // destination URI does not contain "hostname" query param already + $args = array(); + parse_str(isset($parts['query']) ? $parts['query'] : '', $args); + if ($host !== $ip && !isset($args['hostname'])) { + $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . rawurlencode($host); + } + + // append original fragment if known + if (isset($parts['fragment'])) { + $uri .= '#' . $parts['fragment']; + } + + return $connector->connect($uri); + }); + } + + private function resolveHostname($host) + { + if (false !== filter_var($host, FILTER_VALIDATE_IP)) { + return Promise\resolve($host); + } + + $promise = $this->resolver->resolve($host); + + return new Promise\Promise( + function ($resolve, $reject) use ($promise) { + // resolve/reject with result of DNS lookup + $promise->then($resolve, $reject); + }, + function ($_, $reject) use ($promise) { + // cancellation should reject connection attempt + $reject(new RuntimeException('Connection attempt cancelled during DNS lookup')); + + // (try to) cancel pending DNS lookup + if ($promise instanceof CancellablePromiseInterface) { + $promise->cancel(); + } + } + ); + } +} diff --git a/assets/php/vendor/react/socket/src/FixedUriConnector.php b/assets/php/vendor/react/socket/src/FixedUriConnector.php new file mode 100644 index 0000000..057bcdf --- /dev/null +++ b/assets/php/vendor/react/socket/src/FixedUriConnector.php @@ -0,0 +1,41 @@ +connect('localhost:80'); + * ``` + */ +class FixedUriConnector implements ConnectorInterface +{ + private $uri; + private $connector; + + /** + * @param string $uri + * @param ConnectorInterface $connector + */ + public function __construct($uri, ConnectorInterface $connector) + { + $this->uri = $uri; + $this->connector = $connector; + } + + public function connect($_) + { + return $this->connector->connect($this->uri); + } +} diff --git a/assets/php/vendor/react/socket/src/LimitingServer.php b/assets/php/vendor/react/socket/src/LimitingServer.php new file mode 100644 index 0000000..c7874ee --- /dev/null +++ b/assets/php/vendor/react/socket/src/LimitingServer.php @@ -0,0 +1,203 @@ +on('connection', function (ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + */ +class LimitingServer extends EventEmitter implements ServerInterface +{ + private $connections = array(); + private $server; + private $limit; + + private $pauseOnLimit = false; + private $autoPaused = false; + private $manuPaused = false; + + /** + * Instantiates a new LimitingServer. + * + * You have to pass a maximum number of open connections to ensure + * the server will automatically reject (close) connections once this limit + * is exceeded. In this case, it will emit an `error` event to inform about + * this and no `connection` event will be emitted. + * + * ```php + * $server = new LimitingServer($server, 100); + * $server->on('connection', function (ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * You MAY pass a `null` limit in order to put no limit on the number of + * open connections and keep accepting new connection until you run out of + * operating system resources (such as open file handles). This may be + * useful if you do not want to take care of applying a limit but still want + * to use the `getConnections()` method. + * + * You can optionally configure the server to pause accepting new + * connections once the connection limit is reached. In this case, it will + * pause the underlying server and no longer process any new connections at + * all, thus also no longer closing any excessive connections. + * The underlying operating system is responsible for keeping a backlog of + * pending connections until its limit is reached, at which point it will + * start rejecting further connections. + * Once the server is below the connection limit, it will continue consuming + * connections from the backlog and will process any outstanding data on + * each connection. + * This mode may be useful for some protocols that are designed to wait for + * a response message (such as HTTP), but may be less useful for other + * protocols that demand immediate responses (such as a "welcome" message in + * an interactive chat). + * + * ```php + * $server = new LimitingServer($server, 100, true); + * $server->on('connection', function (ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * @param ServerInterface $server + * @param int|null $connectionLimit + * @param bool $pauseOnLimit + */ + public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false) + { + $this->server = $server; + $this->limit = $connectionLimit; + if ($connectionLimit !== null) { + $this->pauseOnLimit = $pauseOnLimit; + } + + $this->server->on('connection', array($this, 'handleConnection')); + $this->server->on('error', array($this, 'handleError')); + } + + /** + * Returns an array with all currently active connections + * + * ```php + * foreach ($server->getConnection() as $connection) { + * $connection->write('Hi!'); + * } + * ``` + * + * @return ConnectionInterface[] + */ + public function getConnections() + { + return $this->connections; + } + + public function getAddress() + { + return $this->server->getAddress(); + } + + public function pause() + { + if (!$this->manuPaused) { + $this->manuPaused = true; + + if (!$this->autoPaused) { + $this->server->pause(); + } + } + } + + public function resume() + { + if ($this->manuPaused) { + $this->manuPaused = false; + + if (!$this->autoPaused) { + $this->server->resume(); + } + } + } + + public function close() + { + $this->server->close(); + } + + /** @internal */ + public function handleConnection(ConnectionInterface $connection) + { + // close connection if limit exceeded + if ($this->limit !== null && count($this->connections) >= $this->limit) { + $this->handleError(new OverflowException('Connection closed because server reached connection limit')); + $connection->close(); + return; + } + + $this->connections[] = $connection; + $that = $this; + $connection->on('close', function () use ($that, $connection) { + $that->handleDisconnection($connection); + }); + + // pause accepting new connections if limit exceeded + if ($this->pauseOnLimit && !$this->autoPaused && count($this->connections) >= $this->limit) { + $this->autoPaused = true; + + if (!$this->manuPaused) { + $this->server->pause(); + } + } + + $this->emit('connection', array($connection)); + } + + /** @internal */ + public function handleDisconnection(ConnectionInterface $connection) + { + unset($this->connections[array_search($connection, $this->connections)]); + + // continue accepting new connection if below limit + if ($this->autoPaused && count($this->connections) < $this->limit) { + $this->autoPaused = false; + + if (!$this->manuPaused) { + $this->server->resume(); + } + } + } + + /** @internal */ + public function handleError(Exception $error) + { + $this->emit('error', array($error)); + } +} diff --git a/assets/php/vendor/react/socket/src/SecureConnector.php b/assets/php/vendor/react/socket/src/SecureConnector.php new file mode 100644 index 0000000..f04183d --- /dev/null +++ b/assets/php/vendor/react/socket/src/SecureConnector.php @@ -0,0 +1,64 @@ +connector = $connector; + $this->streamEncryption = new StreamEncryption($loop, false); + $this->context = $context; + } + + public function connect($uri) + { + if (!function_exists('stream_socket_enable_crypto')) { + return Promise\reject(new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore + } + + if (strpos($uri, '://') === false) { + $uri = 'tls://' . $uri; + } + + $parts = parse_url($uri); + if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') { + return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid')); + } + + $uri = str_replace('tls://', '', $uri); + $context = $this->context; + + $encryption = $this->streamEncryption; + return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption) { + // (unencrypted) TCP/IP connection succeeded + + if (!$connection instanceof Connection) { + $connection->close(); + throw new UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource'); + } + + // set required SSL/TLS context options + foreach ($context as $name => $value) { + stream_context_set_option($connection->stream, 'ssl', $name, $value); + } + + // try to enable encryption + return $encryption->enable($connection)->then(null, function ($error) use ($connection) { + // establishing encryption failed => close invalid connection and return error + $connection->close(); + throw $error; + }); + }); + } +} diff --git a/assets/php/vendor/react/socket/src/SecureServer.php b/assets/php/vendor/react/socket/src/SecureServer.php new file mode 100644 index 0000000..302ae93 --- /dev/null +++ b/assets/php/vendor/react/socket/src/SecureServer.php @@ -0,0 +1,192 @@ +on('connection', function (ConnectionInterface $connection) { + * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; + * + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * Whenever a client fails to perform a successful TLS handshake, it will emit an + * `error` event and then close the underlying TCP/IP connection: + * + * ```php + * $server->on('error', function (Exception $e) { + * echo 'Error' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * Note that the `SecureServer` class is a concrete implementation for TLS sockets. + * If you want to typehint in your higher-level protocol implementation, you SHOULD + * use the generic `ServerInterface` instead. + * + * @see ServerInterface + * @see ConnectionInterface + */ +final class SecureServer extends EventEmitter implements ServerInterface +{ + private $tcp; + private $encryption; + private $context; + + /** + * Creates a secure TLS server and starts waiting for incoming connections + * + * It does so by wrapping a `TcpServer` instance which waits for plaintext + * TCP/IP connections and then performs a TLS handshake for each connection. + * It thus requires valid [TLS context options], + * which in its most basic form may look something like this if you're using a + * PEM encoded certificate file: + * + * ```php + * $server = new TcpServer(8000, $loop); + * $server = new SecureServer($server, $loop, array( + * 'local_cert' => 'server.pem' + * )); + * ``` + * + * Note that the certificate file will not be loaded on instantiation but when an + * incoming connection initializes its TLS context. + * This implies that any invalid certificate file paths or contents will only cause + * an `error` event at a later time. + * + * If your private key is encrypted with a passphrase, you have to specify it + * like this: + * + * ```php + * $server = new TcpServer(8000, $loop); + * $server = new SecureServer($server, $loop, array( + * 'local_cert' => 'server.pem', + * 'passphrase' => 'secret' + * )); + * ``` + * + * Note that available [TLS context options], + * their defaults and effects of changing these may vary depending on your system + * and/or PHP version. + * Passing unknown context options has no effect. + * + * Advanced usage: Despite allowing any `ServerInterface` as first parameter, + * you SHOULD pass a `TcpServer` instance as first parameter, unless you + * know what you're doing. + * Internally, the `SecureServer` has to set the required TLS context options on + * the underlying stream resources. + * These resources are not exposed through any of the interfaces defined in this + * package, but only through the internal `Connection` class. + * The `TcpServer` class is guaranteed to emit connections that implement + * the `ConnectionInterface` and uses the internal `Connection` class in order to + * expose these underlying resources. + * If you use a custom `ServerInterface` and its `connection` event does not + * meet this requirement, the `SecureServer` will emit an `error` event and + * then close the underlying connection. + * + * @param ServerInterface|TcpServer $tcp + * @param LoopInterface $loop + * @param array $context + * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support + * @see TcpServer + * @link http://php.net/manual/en/context.ssl.php for TLS context options + */ + public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context) + { + if (!function_exists('stream_socket_enable_crypto')) { + throw new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore + } + + // default to empty passphrase to suppress blocking passphrase prompt + $context += array( + 'passphrase' => '' + ); + + $this->tcp = $tcp; + $this->encryption = new StreamEncryption($loop); + $this->context = $context; + + $that = $this; + $this->tcp->on('connection', function ($connection) use ($that) { + $that->handleConnection($connection); + }); + $this->tcp->on('error', function ($error) use ($that) { + $that->emit('error', array($error)); + }); + } + + public function getAddress() + { + $address = $this->tcp->getAddress(); + if ($address === null) { + return null; + } + + return str_replace('tcp://' , 'tls://', $address); + } + + public function pause() + { + $this->tcp->pause(); + } + + public function resume() + { + $this->tcp->resume(); + } + + public function close() + { + return $this->tcp->close(); + } + + /** @internal */ + public function handleConnection(ConnectionInterface $connection) + { + if (!$connection instanceof Connection) { + $this->emit('error', array(new UnexpectedValueException('Base server does not use internal Connection class exposing stream resource'))); + $connection->end(); + return; + } + + foreach ($this->context as $name => $value) { + stream_context_set_option($connection->stream, 'ssl', $name, $value); + } + + $that = $this; + + $this->encryption->enable($connection)->then( + function ($conn) use ($that) { + $that->emit('connection', array($conn)); + }, + function ($error) use ($that, $connection) { + $that->emit('error', array($error)); + $connection->end(); + } + ); + } +} diff --git a/assets/php/vendor/react/socket/src/Server.php b/assets/php/vendor/react/socket/src/Server.php new file mode 100644 index 0000000..72712e4 --- /dev/null +++ b/assets/php/vendor/react/socket/src/Server.php @@ -0,0 +1,73 @@ + $context); + } + + // apply default options if not explicitly given + $context += array( + 'tcp' => array(), + 'tls' => array(), + 'unix' => array() + ); + + $scheme = 'tcp'; + $pos = strpos($uri, '://'); + if ($pos !== false) { + $scheme = substr($uri, 0, $pos); + } + + if ($scheme === 'unix') { + $server = new UnixServer($uri, $loop, $context['unix']); + } else { + $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']); + + if ($scheme === 'tls') { + $server = new SecureServer($server, $loop, $context['tls']); + } + } + + $this->server = $server; + + $that = $this; + $server->on('connection', function (ConnectionInterface $conn) use ($that) { + $that->emit('connection', array($conn)); + }); + $server->on('error', function (Exception $error) use ($that) { + $that->emit('error', array($error)); + }); + } + + public function getAddress() + { + return $this->server->getAddress(); + } + + public function pause() + { + $this->server->pause(); + } + + public function resume() + { + $this->server->resume(); + } + + public function close() + { + $this->server->close(); + } +} diff --git a/assets/php/vendor/react/socket/src/ServerInterface.php b/assets/php/vendor/react/socket/src/ServerInterface.php new file mode 100644 index 0000000..5319678 --- /dev/null +++ b/assets/php/vendor/react/socket/src/ServerInterface.php @@ -0,0 +1,151 @@ +on('connection', function (ConnectionInterface $connection) { + * echo 'new connection' . PHP_EOL; + * }); + * ``` + * + * See also the `ConnectionInterface` for more details about handling the + * incoming connection. + * + * error event: + * The `error` event will be emitted whenever there's an error accepting a new + * connection from a client. + * + * ```php + * $server->on('error', function (Exception $e) { + * echo 'error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * Note that this is not a fatal error event, i.e. the server keeps listening for + * new connections even after this event. + * + * @see ConnectionInterface + */ +interface ServerInterface extends EventEmitterInterface +{ + /** + * Returns the full address (URI) this server is currently listening on + * + * ```php + * $address = $server->getAddress(); + * echo 'Server listening on ' . $address . PHP_EOL; + * ``` + * + * If the address can not be determined or is unknown at this time (such as + * after the socket has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * If this is a TCP/IP based server and you only want the local port, you may + * use something like this: + * + * ```php + * $address = $server->getAddress(); + * $port = parse_url($address, PHP_URL_PORT); + * echo 'Server listening on port ' . $port . PHP_EOL; + * ``` + * + * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed) + */ + public function getAddress(); + + /** + * Pauses accepting new incoming connections. + * + * Removes the socket resource from the EventLoop and thus stop accepting + * new connections. Note that the listening socket stays active and is not + * closed. + * + * This means that new incoming connections will stay pending in the + * operating system backlog until its configurable backlog is filled. + * Once the backlog is filled, the operating system may reject further + * incoming connections until the backlog is drained again by resuming + * to accept new connections. + * + * Once the server is paused, no futher `connection` events SHOULD + * be emitted. + * + * ```php + * $server->pause(); + * + * $server->on('connection', assertShouldNeverCalled()); + * ``` + * + * This method is advisory-only, though generally not recommended, the + * server MAY continue emitting `connection` events. + * + * Unless otherwise noted, a successfully opened server SHOULD NOT start + * in paused state. + * + * You can continue processing events by calling `resume()` again. + * + * Note that both methods can be called any number of times, in particular + * calling `pause()` more than once SHOULD NOT have any effect. + * Similarly, calling this after `close()` is a NO-OP. + * + * @see self::resume() + * @return void + */ + public function pause(); + + /** + * Resumes accepting new incoming connections. + * + * Re-attach the socket resource to the EventLoop after a previous `pause()`. + * + * ```php + * $server->pause(); + * + * $loop->addTimer(1.0, function () use ($server) { + * $server->resume(); + * }); + * ``` + * + * Note that both methods can be called any number of times, in particular + * calling `resume()` without a prior `pause()` SHOULD NOT have any effect. + * Similarly, calling this after `close()` is a NO-OP. + * + * @see self::pause() + * @return void + */ + public function resume(); + + /** + * Shuts down this listening socket + * + * This will stop listening for new incoming connections on this socket. + * + * Calling this method more than once on the same instance is a NO-OP. + * + * @return void + */ + public function close(); +} diff --git a/assets/php/vendor/react/socket/src/StreamEncryption.php b/assets/php/vendor/react/socket/src/StreamEncryption.php new file mode 100644 index 0000000..ba5d472 --- /dev/null +++ b/assets/php/vendor/react/socket/src/StreamEncryption.php @@ -0,0 +1,146 @@ +loop = $loop; + $this->server = $server; + + // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3. + // PHP 5.6+ supports bitmasks, legacy PHP only supports predefined + // constants, so apply accordingly below. + // Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did + // only support TLSv1.0, so we explicitly apply all versions. + // @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method + // @link https://3v4l.org/plbFn + if ($server) { + $this->method = STREAM_CRYPTO_METHOD_TLS_SERVER; + + if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_SERVER; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_SERVER; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; + } + } else { + $this->method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + } + } + + public function enable(Connection $stream) + { + return $this->toggle($stream, true); + } + + public function disable(Connection $stream) + { + return $this->toggle($stream, false); + } + + public function toggle(Connection $stream, $toggle) + { + // pause actual stream instance to continue operation on raw stream socket + $stream->pause(); + + // TODO: add write() event to make sure we're not sending any excessive data + + $deferred = new Deferred(function ($_, $reject) use ($toggle) { + // cancelling this leaves this stream in an inconsistent state… + $reject(new RuntimeException('Cancelled toggling encryption ' . $toggle ? 'on' : 'off')); + }); + + // get actual stream socket from stream instance + $socket = $stream->stream; + + // get crypto method from context options or use global setting from constructor + $method = $this->method; + $context = stream_context_get_options($socket); + if (isset($context['ssl']['crypto_method'])) { + $method = $context['ssl']['crypto_method']; + } + + $that = $this; + $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) { + $that->toggleCrypto($socket, $deferred, $toggle, $method); + }; + + $this->loop->addReadStream($socket, $toggleCrypto); + + if (!$this->server) { + $toggleCrypto(); + } + + $loop = $this->loop; + + return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) { + $loop->removeReadStream($socket); + + $stream->encryptionEnabled = $toggle; + $stream->resume(); + + return $stream; + }, function($error) use ($stream, $socket, $loop) { + $loop->removeReadStream($socket); + $stream->resume(); + throw $error; + }); + } + + public function toggleCrypto($socket, Deferred $deferred, $toggle, $method) + { + set_error_handler(array($this, 'handleError')); + $result = stream_socket_enable_crypto($socket, $toggle, $method); + restore_error_handler(); + + if (true === $result) { + $deferred->resolve(); + } else if (false === $result) { + $deferred->reject(new UnexpectedValueException( + sprintf("Unable to complete SSL/TLS handshake: %s", $this->errstr), + $this->errno + )); + } else { + // need more data, will retry + } + } + + public function handleError($errno, $errstr) + { + $this->errstr = str_replace(array("\r", "\n"), ' ', $errstr); + $this->errno = $errno; + } +} diff --git a/assets/php/vendor/react/socket/src/TcpConnector.php b/assets/php/vendor/react/socket/src/TcpConnector.php new file mode 100644 index 0000000..90d7df1 --- /dev/null +++ b/assets/php/vendor/react/socket/src/TcpConnector.php @@ -0,0 +1,122 @@ +loop = $loop; + $this->context = $context; + } + + public function connect($uri) + { + if (strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + } + + $parts = parse_url($uri); + if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { + return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid')); + } + + $ip = trim($parts['host'], '[]'); + if (false === filter_var($ip, FILTER_VALIDATE_IP)) { + return Promise\reject(new InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP')); + } + + // use context given in constructor + $context = array( + 'socket' => $this->context + ); + + // parse arguments from query component of URI + $args = array(); + if (isset($parts['query'])) { + parse_str($parts['query'], $args); + } + + // If an original hostname has been given, use this for TLS setup. + // This can happen due to layers of nested connectors, such as a + // DnsConnector reporting its original hostname. + // These context options are here in case TLS is enabled later on this stream. + // If TLS is not enabled later, this doesn't hurt either. + if (isset($args['hostname'])) { + $context['ssl'] = array( + 'SNI_enabled' => true, + 'peer_name' => $args['hostname'] + ); + + // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead. + // The SNI_server_name context option has to be set here during construction, + // as legacy PHP ignores any values set later. + if (PHP_VERSION_ID < 50600) { + $context['ssl'] += array( + 'SNI_server_name' => $args['hostname'], + 'CN_match' => $args['hostname'] + ); + } + } + + // latest versions of PHP no longer accept any other URI components and + // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here + $remote = 'tcp://' . $parts['host'] . ':' . $parts['port']; + + $socket = @stream_socket_client( + $remote, + $errno, + $errstr, + 0, + STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT, + stream_context_create($context) + ); + + if (false === $socket) { + return Promise\reject(new RuntimeException( + sprintf("Connection to %s failed: %s", $uri, $errstr), + $errno + )); + } + + stream_set_blocking($socket, 0); + + // wait for connection + + return $this->waitForStreamOnce($socket); + } + + private function waitForStreamOnce($stream) + { + $loop = $this->loop; + + return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) { + $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) { + $loop->removeWriteStream($stream); + + // The following hack looks like the only way to + // detect connection refused errors with PHP's stream sockets. + if (false === stream_socket_get_name($stream, true)) { + fclose($stream); + + $reject(new RuntimeException('Connection refused')); + } else { + $resolve(new Connection($stream, $loop)); + } + }); + }, function () use ($loop, $stream) { + $loop->removeWriteStream($stream); + fclose($stream); + + throw new RuntimeException('Cancelled while waiting for TCP/IP connection to be established'); + }); + } +} diff --git a/assets/php/vendor/react/socket/src/TcpServer.php b/assets/php/vendor/react/socket/src/TcpServer.php new file mode 100644 index 0000000..119e177 --- /dev/null +++ b/assets/php/vendor/react/socket/src/TcpServer.php @@ -0,0 +1,236 @@ +on('connection', function (ConnectionInterface $connection) { + * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + */ +final class TcpServer extends EventEmitter implements ServerInterface +{ + private $master; + private $loop; + private $listening = false; + + /** + * Creates a plaintext TCP/IP socket server and starts listening on the given address + * + * This starts accepting new incoming connections on the given address. + * See also the `connection event` documented in the `ServerInterface` + * for more details. + * + * ```php + * $server = new TcpServer(8080, $loop); + * ``` + * + * As above, the `$uri` parameter can consist of only a port, in which case the + * server will default to listening on the localhost address `127.0.0.1`, + * which means it will not be reachable from outside of this system. + * + * In order to use a random port assignment, you can use the port `0`: + * + * ```php + * $server = new TcpServer(0, $loop); + * $address = $server->getAddress(); + * ``` + * + * In order to change the host the socket is listening on, you can provide an IP + * address through the first parameter provided to the constructor, optionally + * preceded by the `tcp://` scheme: + * + * ```php + * $server = new TcpServer('192.168.0.1:8080', $loop); + * ``` + * + * If you want to listen on an IPv6 address, you MUST enclose the host in square + * brackets: + * + * ```php + * $server = new TcpServer('[::1]:8080', $loop); + * ``` + * + * If the given URI is invalid, does not contain a port, any other scheme or if it + * contains a hostname, it will throw an `InvalidArgumentException`: + * + * ```php + * // throws InvalidArgumentException due to missing port + * $server = new TcpServer('127.0.0.1', $loop); + * ``` + * + * If the given URI appears to be valid, but listening on it fails (such as if port + * is already in use or port below 1024 may require root access etc.), it will + * throw a `RuntimeException`: + * + * ```php + * $first = new TcpServer(8080, $loop); + * + * // throws RuntimeException because port is already in use + * $second = new TcpServer(8080, $loop); + * ``` + * + * Note that these error conditions may vary depending on your system and/or + * configuration. + * See the exception message and code for more details about the actual error + * condition. + * + * Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php) + * for the underlying stream socket resource like this: + * + * ```php + * $server = new TcpServer('[::1]:8080', $loop, array( + * 'backlog' => 200, + * 'so_reuseport' => true, + * 'ipv6_v6only' => true + * )); + * ``` + * + * Note that available [socket context options](http://php.net/manual/en/context.socket.php), + * their defaults and effects of changing these may vary depending on your system + * and/or PHP version. + * Passing unknown context options has no effect. + * + * @param string|int $uri + * @param LoopInterface $loop + * @param array $context + * @throws InvalidArgumentException if the listening address is invalid + * @throws RuntimeException if listening on this address fails (already in use etc.) + */ + public function __construct($uri, LoopInterface $loop, array $context = array()) + { + $this->loop = $loop; + + // a single port has been given => assume localhost + if ((string)(int)$uri === (string)$uri) { + $uri = '127.0.0.1:' . $uri; + } + + // assume default scheme if none has been given + if (strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + } + + // parse_url() does not accept null ports (random port assignment) => manually remove + if (substr($uri, -2) === ':0') { + $parts = parse_url(substr($uri, 0, -2)); + if ($parts) { + $parts['port'] = 0; + } + } else { + $parts = parse_url($uri); + } + + // ensure URI contains TCP scheme, host and port + if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { + throw new InvalidArgumentException('Invalid URI "' . $uri . '" given'); + } + + if (false === filter_var(trim($parts['host'], '[]'), FILTER_VALIDATE_IP)) { + throw new InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP'); + } + + $this->master = @stream_socket_server( + $uri, + $errno, + $errstr, + STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, + stream_context_create(array('socket' => $context)) + ); + if (false === $this->master) { + throw new RuntimeException('Failed to listen on "' . $uri . '": ' . $errstr, $errno); + } + stream_set_blocking($this->master, 0); + + $this->resume(); + } + + public function getAddress() + { + if (!is_resource($this->master)) { + return null; + } + + $address = stream_socket_get_name($this->master, false); + + // check if this is an IPv6 address which includes multiple colons but no square brackets + $pos = strrpos($address, ':'); + if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') { + $port = substr($address, $pos + 1); + $address = '[' . substr($address, 0, $pos) . ']:' . $port; + } + + return 'tcp://' . $address; + } + + public function pause() + { + if (!$this->listening) { + return; + } + + $this->loop->removeReadStream($this->master); + $this->listening = false; + } + + public function resume() + { + if ($this->listening || !is_resource($this->master)) { + return; + } + + $that = $this; + $this->loop->addReadStream($this->master, function ($master) use ($that) { + $newSocket = @stream_socket_accept($master); + if (false === $newSocket) { + $that->emit('error', array(new RuntimeException('Error accepting new connection'))); + + return; + } + $that->handleConnection($newSocket); + }); + $this->listening = true; + } + + public function close() + { + if (!is_resource($this->master)) { + return; + } + + $this->pause(); + fclose($this->master); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleConnection($socket) + { + $this->emit('connection', array( + new Connection($socket, $this->loop) + )); + } +} diff --git a/assets/php/vendor/react/socket/src/TimeoutConnector.php b/assets/php/vendor/react/socket/src/TimeoutConnector.php new file mode 100644 index 0000000..d4eba2e --- /dev/null +++ b/assets/php/vendor/react/socket/src/TimeoutConnector.php @@ -0,0 +1,25 @@ +connector = $connector; + $this->timeout = $timeout; + $this->loop = $loop; + } + + public function connect($uri) + { + return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop); + } +} diff --git a/assets/php/vendor/react/socket/src/UnixConnector.php b/assets/php/vendor/react/socket/src/UnixConnector.php new file mode 100644 index 0000000..9b84ab0 --- /dev/null +++ b/assets/php/vendor/react/socket/src/UnixConnector.php @@ -0,0 +1,44 @@ +loop = $loop; + } + + public function connect($path) + { + if (strpos($path, '://') === false) { + $path = 'unix://' . $path; + } elseif (substr($path, 0, 7) !== 'unix://') { + return Promise\reject(new InvalidArgumentException('Given URI "' . $path . '" is invalid')); + } + + $resource = @stream_socket_client($path, $errno, $errstr, 1.0); + + if (!$resource) { + return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno)); + } + + $connection = new Connection($resource, $this->loop); + $connection->unix = true; + + return Promise\resolve($connection); + } +} diff --git a/assets/php/vendor/react/socket/src/UnixServer.php b/assets/php/vendor/react/socket/src/UnixServer.php new file mode 100644 index 0000000..8f1ed98 --- /dev/null +++ b/assets/php/vendor/react/socket/src/UnixServer.php @@ -0,0 +1,130 @@ +loop = $loop; + + if (strpos($path, '://') === false) { + $path = 'unix://' . $path; + } elseif (substr($path, 0, 7) !== 'unix://') { + throw new InvalidArgumentException('Given URI "' . $path . '" is invalid'); + } + + $this->master = @stream_socket_server( + $path, + $errno, + $errstr, + STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, + stream_context_create(array('socket' => $context)) + ); + if (false === $this->master) { + throw new RuntimeException('Failed to listen on unix domain socket "' . $path . '": ' . $errstr, $errno); + } + stream_set_blocking($this->master, 0); + + $this->resume(); + } + + public function getAddress() + { + if (!is_resource($this->master)) { + return null; + } + + return 'unix://' . stream_socket_get_name($this->master, false); + } + + public function pause() + { + if (!$this->listening) { + return; + } + + $this->loop->removeReadStream($this->master); + $this->listening = false; + } + + public function resume() + { + if ($this->listening || !is_resource($this->master)) { + return; + } + + $that = $this; + $this->loop->addReadStream($this->master, function ($master) use ($that) { + $newSocket = @stream_socket_accept($master); + if (false === $newSocket) { + $that->emit('error', array(new RuntimeException('Error accepting new connection'))); + + return; + } + $that->handleConnection($newSocket); + }); + $this->listening = true; + } + + public function close() + { + if (!is_resource($this->master)) { + return; + } + + $this->pause(); + fclose($this->master); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleConnection($socket) + { + $connection = new Connection($socket, $this->loop); + $connection->unix = true; + + $this->emit('connection', array( + $connection + )); + } +} diff --git a/assets/php/vendor/react/socket/tests/ConnectionTest.php b/assets/php/vendor/react/socket/tests/ConnectionTest.php new file mode 100644 index 0000000..d3563df --- /dev/null +++ b/assets/php/vendor/react/socket/tests/ConnectionTest.php @@ -0,0 +1,47 @@ +markTestSkipped('HHVM does not support socket operation on test memory stream'); + } + + $resource = fopen('php://memory', 'r+'); + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $connection = new Connection($resource, $loop); + $connection->close(); + + $this->assertFalse(is_resource($resource)); + } + + public function testCloseConnectionWillRemoveResourceFromLoopBeforeClosingResource() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM does not support socket operation on test memory stream'); + } + + $resource = fopen('php://memory', 'r+'); + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('addWriteStream')->with($resource); + + $onRemove = null; + $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($param) use (&$onRemove) { + $onRemove = is_resource($param); + return true; + })); + + $connection = new Connection($resource, $loop); + $connection->write('test'); + $connection->close(); + + $this->assertTrue($onRemove); + $this->assertFalse(is_resource($resource)); + } +} diff --git a/assets/php/vendor/react/socket/tests/ConnectorTest.php b/assets/php/vendor/react/socket/tests/ConnectorTest.php new file mode 100644 index 0000000..c8eb19b --- /dev/null +++ b/assets/php/vendor/react/socket/tests/ConnectorTest.php @@ -0,0 +1,128 @@ +getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $promise = new Promise(function () { }); + $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $tcp->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise); + + $connector = new Connector($loop, array( + 'tcp' => $tcp + )); + + $connector->connect('127.0.0.1:80'); + } + + public function testConnectorPassedThroughHostnameIfDnsIsDisabled() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $promise = new Promise(function () { }); + $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $tcp->expects($this->once())->method('connect')->with('tcp://google.com:80')->willReturn($promise); + + $connector = new Connector($loop, array( + 'tcp' => $tcp, + 'dns' => false + )); + + $connector->connect('tcp://google.com:80'); + } + + public function testConnectorWithUnknownSchemeAlwaysFails() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $connector = new Connector($loop); + + $promise = $connector->connect('unknown://google.com:80'); + $promise->then(null, $this->expectCallableOnce()); + } + + public function testConnectorWithDisabledTcpDefaultSchemeAlwaysFails() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $connector = new Connector($loop, array( + 'tcp' => false + )); + + $promise = $connector->connect('google.com:80'); + $promise->then(null, $this->expectCallableOnce()); + } + + public function testConnectorWithDisabledTcpSchemeAlwaysFails() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $connector = new Connector($loop, array( + 'tcp' => false + )); + + $promise = $connector->connect('tcp://google.com:80'); + $promise->then(null, $this->expectCallableOnce()); + } + + public function testConnectorWithDisabledTlsSchemeAlwaysFails() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $connector = new Connector($loop, array( + 'tls' => false + )); + + $promise = $connector->connect('tls://google.com:443'); + $promise->then(null, $this->expectCallableOnce()); + } + + public function testConnectorWithDisabledUnixSchemeAlwaysFails() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $connector = new Connector($loop, array( + 'unix' => false + )); + + $promise = $connector->connect('unix://demo.sock'); + $promise->then(null, $this->expectCallableOnce()); + } + + public function testConnectorUsesGivenResolverInstance() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $promise = new Promise(function () { }); + $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock(); + $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise); + + $connector = new Connector($loop, array( + 'dns' => $resolver + )); + + $connector->connect('google.com:80'); + } + + public function testConnectorUsesResolvedHostnameIfDnsIsUsed() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $promise = new Promise(function ($resolve) { $resolve('127.0.0.1'); }); + $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock(); + $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise); + + $promise = new Promise(function () { }); + $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $tcp->expects($this->once())->method('connect')->with('tcp://127.0.0.1:80?hostname=google.com')->willReturn($promise); + + $connector = new Connector($loop, array( + 'tcp' => $tcp, + 'dns' => $resolver + )); + + $connector->connect('tcp://google.com:80'); + } +} diff --git a/assets/php/vendor/react/socket/tests/DnsConnectorTest.php b/assets/php/vendor/react/socket/tests/DnsConnectorTest.php new file mode 100644 index 0000000..3c94c39 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/DnsConnectorTest.php @@ -0,0 +1,111 @@ +tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $this->resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock(); + + $this->connector = new DnsConnector($this->tcp, $this->resolver); + } + + public function testPassByResolverIfGivenIp() + { + $this->resolver->expects($this->never())->method('resolve'); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('127.0.0.1:80'); + } + + public function testPassThroughResolverIfGivenHost() + { + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('google.com:80'); + } + + public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6() + { + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1'))); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('google.com:80'); + } + + public function testPassByResolverIfGivenCompleteUri() + { + $this->resolver->expects($this->never())->method('resolve'); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment'); + } + + public function testPassThroughResolverIfGivenCompleteUri() + { + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('scheme://google.com:80/path?query#fragment'); + } + + public function testPassThroughResolverIfGivenExplicitHost() + { + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('scheme://google.com:80/?hostname=google.de'); + } + + public function testRejectsImmediatelyIfUriIsInvalid() + { + $this->resolver->expects($this->never())->method('resolve'); + $this->tcp->expects($this->never())->method('connect'); + + $promise = $this->connector->connect('////'); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } + + public function testSkipConnectionIfDnsFails() + { + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->never())->method('connect'); + + $this->connector->connect('example.invalid:80'); + } + + public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection() + { + $pending = new Promise\Promise(function () { }, $this->expectCallableOnce()); + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue($pending)); + $this->tcp->expects($this->never())->method('connect'); + + $promise = $this->connector->connect('example.com:80'); + $promise->cancel(); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } + + public function testCancelDuringTcpConnectionCancelsTcpConnection() + { + $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); }); + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->will($this->returnValue($pending)); + + $promise = $this->connector->connect('example.com:80'); + $promise->cancel(); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } +} diff --git a/assets/php/vendor/react/socket/tests/FixedUriConnectorTest.php b/assets/php/vendor/react/socket/tests/FixedUriConnectorTest.php new file mode 100644 index 0000000..f42d74f --- /dev/null +++ b/assets/php/vendor/react/socket/tests/FixedUriConnectorTest.php @@ -0,0 +1,19 @@ +getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $base->expects($this->once())->method('connect')->with('test')->willReturn('ret'); + + $connector = new FixedUriConnector('test', $base); + + $this->assertEquals('ret', $connector->connect('ignored')); + } +} diff --git a/assets/php/vendor/react/socket/tests/FunctionalConnectorTest.php b/assets/php/vendor/react/socket/tests/FunctionalConnectorTest.php new file mode 100644 index 0000000..6611352 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/FunctionalConnectorTest.php @@ -0,0 +1,32 @@ +on('connection', $this->expectCallableOnce()); + $server->on('connection', array($server, 'close')); + + $connector = new Connector($loop); + + $connection = Block\await($connector->connect('localhost:9998'), $loop, self::TIMEOUT); + + $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection); + + $connection->close(); + $server->close(); + } +} diff --git a/assets/php/vendor/react/socket/tests/FunctionalSecureServerTest.php b/assets/php/vendor/react/socket/tests/FunctionalSecureServerTest.php new file mode 100644 index 0000000..78a59d0 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/FunctionalSecureServerTest.php @@ -0,0 +1,438 @@ +markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + } + + public function testEmitsConnectionForNewConnection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + Block\await($promise, $loop, self::TIMEOUT); + } + + public function testWritesDataToConnection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableOnce()); + + $server->on('connection', function (ConnectionInterface $conn) { + $conn->write('foo'); + }); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $local = Block\await($promise, $loop, self::TIMEOUT); + /* @var $local ConnectionInterface */ + + $local->on('data', $this->expectCallableOnceWith('foo')); + + Block\sleep(self::TIMEOUT, $loop); + } + + public function testWritesDataInMultipleChunksToConnection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableOnce()); + + $server->on('connection', function (ConnectionInterface $conn) { + $conn->write(str_repeat('*', 400000)); + }); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $local = Block\await($promise, $loop, self::TIMEOUT); + /* @var $local React\Stream\Stream */ + + $received = 0; + $local->on('data', function ($chunk) use (&$received) { + $received += strlen($chunk); + }); + + Block\sleep(self::TIMEOUT, $loop); + + $this->assertEquals(400000, $received); + } + + public function testWritesMoreDataInMultipleChunksToConnection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableOnce()); + + $server->on('connection', function (ConnectionInterface $conn) { + $conn->write(str_repeat('*', 2000000)); + }); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $local = Block\await($promise, $loop, self::TIMEOUT); + /* @var $local React\Stream\Stream */ + + $received = 0; + $local->on('data', function ($chunk) use (&$received) { + $received += strlen($chunk); + }); + + Block\sleep(self::TIMEOUT, $loop); + + $this->assertEquals(2000000, $received); + } + + public function testEmitsDataFromConnection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableOnce()); + + $once = $this->expectCallableOnceWith('foo'); + $server->on('connection', function (ConnectionInterface $conn) use ($once) { + $conn->on('data', $once); + }); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $local = Block\await($promise, $loop, self::TIMEOUT); + /* @var $local React\Stream\Stream */ + + $local->write("foo"); + + Block\sleep(self::TIMEOUT, $loop); + } + + public function testEmitsDataInMultipleChunksFromConnection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableOnce()); + + $received = 0; + $server->on('connection', function (ConnectionInterface $conn) use (&$received) { + $conn->on('data', function ($chunk) use (&$received) { + $received += strlen($chunk); + }); + }); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $local = Block\await($promise, $loop, self::TIMEOUT); + /* @var $local React\Stream\Stream */ + + $local->write(str_repeat('*', 400000)); + + Block\sleep(self::TIMEOUT, $loop); + + $this->assertEquals(400000, $received); + } + + public function testPipesDataBackInMultipleChunksFromConnection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableOnce()); + + $server->on('connection', function (ConnectionInterface $conn) use (&$received) { + $conn->pipe($conn); + }); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $local = Block\await($promise, $loop, self::TIMEOUT); + /* @var $local React\Stream\Stream */ + + $received = 0; + $local->on('data', function ($chunk) use (&$received) { + $received += strlen($chunk); + }); + + $local->write(str_repeat('*', 400000)); + + Block\sleep(self::TIMEOUT, $loop); + + $this->assertEquals(400000, $received); + } + + /** + * @requires PHP 5.6 + */ + public function testEmitsConnectionForNewTlsv11Connection() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem', + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER + )); + $server->on('connection', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT + )); + $promise = $connector->connect($server->getAddress()); + + Block\await($promise, $loop, self::TIMEOUT); + } + + /** + * @requires PHP 5.6 + */ + public function testEmitsErrorForClientWithTlsVersionMismatch() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem', + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT + )); + $promise = $connector->connect($server->getAddress()); + + $this->setExpectedException('RuntimeException', 'handshake'); + Block\await($promise, $loop, self::TIMEOUT); + } + + public function testEmitsConnectionForNewConnectionWithEncryptedCertificate() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem', + 'passphrase' => 'swordfish' + )); + $server->on('connection', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + Block\await($promise, $loop, self::TIMEOUT); + } + + public function testEmitsErrorForServerWithInvalidCertificate() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => 'invalid.pem' + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $this->setExpectedException('RuntimeException', 'handshake'); + Block\await($promise, $loop, self::TIMEOUT); + } + + public function testEmitsErrorForServerWithEncryptedCertificateMissingPassphrase() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem' + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $this->setExpectedException('RuntimeException', 'handshake'); + Block\await($promise, $loop, self::TIMEOUT); + } + + public function testEmitsErrorForServerWithEncryptedCertificateWithInvalidPassphrase() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem', + 'passphrase' => 'nope' + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + $this->setExpectedException('RuntimeException', 'handshake'); + Block\await($promise, $loop, self::TIMEOUT); + } + + public function testEmitsErrorForConnectionWithPeerVerification() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => true + )); + $promise = $connector->connect($server->getAddress()); + + $promise->then(null, $this->expectCallableOnce()); + Block\sleep(self::TIMEOUT, $loop); + } + + public function testEmitsErrorIfConnectionIsCancelled() + { + if (PHP_OS !== 'Linux') { + $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')'); + } + + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableOnce()); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + $promise->cancel(); + + $promise->then(null, $this->expectCallableOnce()); + Block\sleep(self::TIMEOUT, $loop); + } + + public function testEmitsNothingIfConnectionIsIdle() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableNever()); + + $connector = new TcpConnector($loop); + $promise = $connector->connect(str_replace('tls://', '', $server->getAddress())); + + $promise->then($this->expectCallableOnce()); + Block\sleep(self::TIMEOUT, $loop); + } + + public function testEmitsErrorIfConnectionIsNotSecureHandshake() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $server->on('connection', $this->expectCallableNever()); + $server->on('error', $this->expectCallableOnce()); + + $connector = new TcpConnector($loop); + $promise = $connector->connect(str_replace('tls://', '', $server->getAddress())); + + $promise->then(function (ConnectionInterface $stream) { + $stream->write("GET / HTTP/1.0\r\n\r\n"); + }); + + Block\sleep(self::TIMEOUT, $loop); + } +} diff --git a/assets/php/vendor/react/socket/tests/FunctionalTcpServerTest.php b/assets/php/vendor/react/socket/tests/FunctionalTcpServerTest.php new file mode 100644 index 0000000..ec7855e --- /dev/null +++ b/assets/php/vendor/react/socket/tests/FunctionalTcpServerTest.php @@ -0,0 +1,324 @@ +on('connection', $this->expectCallableOnce()); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + } + + public function testEmitsNoConnectionForNewConnectionWhenPaused() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server->on('connection', $this->expectCallableNever()); + $server->pause(); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + } + + public function testEmitsConnectionForNewConnectionWhenResumedAfterPause() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server->on('connection', $this->expectCallableOnce()); + $server->pause(); + $server->resume(); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + } + + public function testEmitsConnectionWithRemoteIp() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $peer = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$peer) { + $peer = $conn->getRemoteAddress(); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertContains('127.0.0.1:', $peer); + } + + public function testEmitsConnectionWithLocalIp() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $local = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$local) { + $local = $conn->getLocalAddress(); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertContains('127.0.0.1:', $local); + $this->assertEquals($server->getAddress(), $local); + } + + public function testEmitsConnectionWithLocalIpDespiteListeningOnAll() + { + $loop = Factory::create(); + + $server = new TcpServer('0.0.0.0:0', $loop); + $local = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$local) { + $local = $conn->getLocalAddress(); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertContains('127.0.0.1:', $local); + } + + public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $peer = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$peer) { + $conn->on('close', function () use ($conn, &$peer) { + $peer = $conn->getRemoteAddress(); + }); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $client = Block\await($promise, $loop, 0.1); + $client->end(); + + Block\sleep(0.1, $loop); + + $this->assertContains('127.0.0.1:', $peer); + } + + public function testEmitsConnectionWithRemoteNullAddressAfterConnectionIsClosedLocally() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $peer = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$peer) { + $conn->close(); + $peer = $conn->getRemoteAddress(); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertNull($peer); + } + + public function testEmitsConnectionEvenIfConnectionIsCancelled() + { + if (PHP_OS !== 'Linux') { + $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')'); + } + + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server->on('connection', $this->expectCallableOnce()); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + $promise->cancel(); + + $promise->then(null, $this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + } + + public function testEmitsConnectionForNewIpv6Connection() + { + $loop = Factory::create(); + + try { + $server = new TcpServer('[::1]:0', $loop); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)'); + } + + $server->on('connection', $this->expectCallableOnce()); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + } + + public function testEmitsConnectionWithRemoteIpv6() + { + $loop = Factory::create(); + + try { + $server = new TcpServer('[::1]:0', $loop); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)'); + } + + $peer = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$peer) { + $peer = $conn->getRemoteAddress(); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertContains('[::1]:', $peer); + } + + public function testEmitsConnectionWithLocalIpv6() + { + $loop = Factory::create(); + + try { + $server = new TcpServer('[::1]:0', $loop); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)'); + } + + $local = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$local) { + $local = $conn->getLocalAddress(); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertContains('[::1]:', $local); + $this->assertEquals($server->getAddress(), $local); + } + + public function testEmitsConnectionWithInheritedContextOptions() + { + if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) { + // https://3v4l.org/hB4Tc + $this->markTestSkipped('Not supported on legacy HHVM < 3.13'); + } + + $loop = Factory::create(); + + $server = new TcpServer(0, $loop, array( + 'backlog' => 4 + )); + + $all = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$all) { + $all = stream_context_get_options($conn->stream); + }); + + $connector = new TcpConnector($loop); + $promise = $connector->connect($server->getAddress()); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertEquals(array('socket' => array('backlog' => 4)), $all); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFailsToListenOnInvalidUri() + { + $loop = Factory::create(); + + new TcpServer('///', $loop); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFailsToListenOnUriWithoutPort() + { + $loop = Factory::create(); + + new TcpServer('127.0.0.1', $loop); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFailsToListenOnUriWithWrongScheme() + { + $loop = Factory::create(); + + new TcpServer('udp://127.0.0.1:0', $loop); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFailsToListenOnUriWIthHostname() + { + $loop = Factory::create(); + + new TcpServer('localhost:8080', $loop); + } +} diff --git a/assets/php/vendor/react/socket/tests/IntegrationTest.php b/assets/php/vendor/react/socket/tests/IntegrationTest.php new file mode 100644 index 0000000..24dbe37 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/IntegrationTest.php @@ -0,0 +1,171 @@ +connect('google.com:80'), $loop); + + $this->assertContains(':80', $conn->getRemoteAddress()); + $this->assertNotEquals('google.com:80', $conn->getRemoteAddress()); + + $conn->write("GET / HTTP/1.0\r\n\r\n"); + + $response = $this->buffer($conn, $loop, self::TIMEOUT); + + $this->assertRegExp('#^HTTP/1\.0#', $response); + } + + /** @test */ + public function gettingEncryptedStuffFromGoogleShouldWork() + { + if (!function_exists('stream_socket_enable_crypto')) { + $this->markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $loop = Factory::create(); + $secureConnector = new Connector($loop); + + $conn = Block\await($secureConnector->connect('tls://google.com:443'), $loop); + + $conn->write("GET / HTTP/1.0\r\n\r\n"); + + $response = $this->buffer($conn, $loop, self::TIMEOUT); + + $this->assertRegExp('#^HTTP/1\.0#', $response); + } + + /** @test */ + public function gettingEncryptedStuffFromGoogleShouldWorkIfHostIsResolvedFirst() + { + if (!function_exists('stream_socket_enable_crypto')) { + $this->markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $loop = Factory::create(); + + $factory = new ResolverFactory(); + $dns = $factory->create('8.8.8.8', $loop); + + $connector = new DnsConnector( + new SecureConnector( + new TcpConnector($loop), + $loop + ), + $dns + ); + + $conn = Block\await($connector->connect('google.com:443'), $loop); + + $conn->write("GET / HTTP/1.0\r\n\r\n"); + + $response = $this->buffer($conn, $loop, self::TIMEOUT); + + $this->assertRegExp('#^HTTP/1\.0#', $response); + } + + /** @test */ + public function gettingPlaintextStuffFromEncryptedGoogleShouldNotWork() + { + $loop = Factory::create(); + $connector = new Connector($loop); + + $conn = Block\await($connector->connect('google.com:443'), $loop); + + $this->assertContains(':443', $conn->getRemoteAddress()); + $this->assertNotEquals('google.com:443', $conn->getRemoteAddress()); + + $conn->write("GET / HTTP/1.0\r\n\r\n"); + + $response = $this->buffer($conn, $loop, self::TIMEOUT); + + $this->assertNotRegExp('#^HTTP/1\.0#', $response); + } + + public function testConnectingFailsIfDnsUsesInvalidResolver() + { + $loop = Factory::create(); + + $factory = new ResolverFactory(); + $dns = $factory->create('demo.invalid', $loop); + + $connector = new Connector($loop, array( + 'dns' => $dns + )); + + $this->setExpectedException('RuntimeException'); + Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT); + } + + public function testConnectingFailsIfTimeoutIsTooSmall() + { + if (!function_exists('stream_socket_enable_crypto')) { + $this->markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $loop = Factory::create(); + + $connector = new Connector($loop, array( + 'timeout' => 0.001 + )); + + $this->setExpectedException('RuntimeException'); + Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT); + } + + public function testSelfSignedRejectsIfVerificationIsEnabled() + { + if (!function_exists('stream_socket_enable_crypto')) { + $this->markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $loop = Factory::create(); + + $connector = new Connector($loop, array( + 'tls' => array( + 'verify_peer' => true + ) + )); + + $this->setExpectedException('RuntimeException'); + Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT); + } + + public function testSelfSignedResolvesIfVerificationIsDisabled() + { + if (!function_exists('stream_socket_enable_crypto')) { + $this->markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $loop = Factory::create(); + + $connector = new Connector($loop, array( + 'tls' => array( + 'verify_peer' => false + ) + )); + + $conn = Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT); + $conn->close(); + + // if we reach this, then everything is good + $this->assertNull(null); + } +} diff --git a/assets/php/vendor/react/socket/tests/LimitingServerTest.php b/assets/php/vendor/react/socket/tests/LimitingServerTest.php new file mode 100644 index 0000000..2cc9a58 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/LimitingServerTest.php @@ -0,0 +1,195 @@ +getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('getAddress')->willReturn('127.0.0.1:1234'); + + $server = new LimitingServer($tcp, 100); + + $this->assertEquals('127.0.0.1:1234', $server->getAddress()); + } + + public function testPauseWillBePassedThroughToTcpServer() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('pause'); + + $server = new LimitingServer($tcp, 100); + + $server->pause(); + } + + public function testPauseTwiceWillBePassedThroughToTcpServerOnce() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('pause'); + + $server = new LimitingServer($tcp, 100); + + $server->pause(); + $server->pause(); + } + + public function testResumeWillBePassedThroughToTcpServer() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('resume'); + + $server = new LimitingServer($tcp, 100); + + $server->pause(); + $server->resume(); + } + + public function testResumeTwiceWillBePassedThroughToTcpServerOnce() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('resume'); + + $server = new LimitingServer($tcp, 100); + + $server->pause(); + $server->resume(); + $server->resume(); + } + + public function testCloseWillBePassedThroughToTcpServer() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('close'); + + $server = new LimitingServer($tcp, 100); + + $server->close(); + } + + public function testSocketErrorWillBeForwarded() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $tcp = new TcpServer(0, $loop); + + $server = new LimitingServer($tcp, 100); + + $server->on('error', $this->expectCallableOnce()); + + $tcp->emit('error', array(new \RuntimeException('test'))); + } + + public function testSocketConnectionWillBeForwarded() + { + $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $tcp = new TcpServer(0, $loop); + + $server = new LimitingServer($tcp, 100); + $server->on('connection', $this->expectCallableOnceWith($connection)); + $server->on('error', $this->expectCallableNever()); + + $tcp->emit('connection', array($connection)); + + $this->assertEquals(array($connection), $server->getConnections()); + } + + public function testSocketConnectionWillBeClosedOnceLimitIsReached() + { + $first = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); + $first->expects($this->never())->method('close'); + $second = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); + $second->expects($this->once())->method('close'); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $tcp = new TcpServer(0, $loop); + + $server = new LimitingServer($tcp, 1); + $server->on('connection', $this->expectCallableOnceWith($first)); + $server->on('error', $this->expectCallableOnce()); + + $tcp->emit('connection', array($first)); + $tcp->emit('connection', array($second)); + } + + public function testPausingServerWillBePausedOnceLimitIsReached() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('addReadStream'); + $loop->expects($this->once())->method('removeReadStream'); + + $tcp = new TcpServer(0, $loop); + + $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); + + $server = new LimitingServer($tcp, 1, true); + + $tcp->emit('connection', array($connection)); + } + + public function testSocketDisconnectionWillRemoveFromList() + { + $loop = Factory::create(); + + $tcp = new TcpServer(0, $loop); + + $socket = stream_socket_client($tcp->getAddress()); + fclose($socket); + + $server = new LimitingServer($tcp, 100); + $server->on('connection', $this->expectCallableOnce()); + $server->on('error', $this->expectCallableNever()); + + Block\sleep(0.1, $loop); + + $this->assertEquals(array(), $server->getConnections()); + } + + public function testPausingServerWillEmitOnlyOneButAcceptTwoConnectionsDueToOperatingSystem() + { + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new LimitingServer($server, 1, true); + $server->on('connection', $this->expectCallableOnce()); + $server->on('error', $this->expectCallableNever()); + + $first = stream_socket_client($server->getAddress()); + $second = stream_socket_client($server->getAddress()); + + Block\sleep(0.1, $loop); + + fclose($first); + fclose($second); + } + + public function testPausingServerWillEmitTwoConnectionsFromBacklog() + { + $loop = Factory::create(); + + $twice = $this->createCallableMock(); + $twice->expects($this->exactly(2))->method('__invoke'); + + $server = new TcpServer(0, $loop); + $server = new LimitingServer($server, 1, true); + $server->on('connection', $twice); + $server->on('error', $this->expectCallableNever()); + + $first = stream_socket_client($server->getAddress()); + fclose($first); + $second = stream_socket_client($server->getAddress()); + fclose($second); + + Block\sleep(0.1, $loop); + } +} diff --git a/assets/php/vendor/react/socket/tests/SecureConnectorTest.php b/assets/php/vendor/react/socket/tests/SecureConnectorTest.php new file mode 100644 index 0000000..0b3a702 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/SecureConnectorTest.php @@ -0,0 +1,74 @@ +markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $this->tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $this->connector = new SecureConnector($this->tcp, $this->loop); + } + + public function testConnectionWillWaitForTcpConnection() + { + $pending = new Promise\Promise(function () { }); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending)); + + $promise = $this->connector->connect('example.com:80'); + + $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); + } + + public function testConnectionWithCompleteUriWillBePassedThroughExpectForScheme() + { + $pending = new Promise\Promise(function () { }); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80/path?query#fragment'))->will($this->returnValue($pending)); + + $this->connector->connect('tls://example.com:80/path?query#fragment'); + } + + public function testConnectionToInvalidSchemeWillReject() + { + $this->tcp->expects($this->never())->method('connect'); + + $promise = $this->connector->connect('tcp://example.com:80'); + + $promise->then(null, $this->expectCallableOnce()); + } + + public function testCancelDuringTcpConnectionCancelsTcpConnection() + { + $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); }); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending)); + + $promise = $this->connector->connect('example.com:80'); + $promise->cancel(); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } + + public function testConnectionWillBeClosedAndRejectedIfConnectioIsNoStream() + { + $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); + $connection->expects($this->once())->method('close'); + + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->willReturn(Promise\resolve($connection)); + + $promise = $this->connector->connect('example.com:80'); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } +} diff --git a/assets/php/vendor/react/socket/tests/SecureIntegrationTest.php b/assets/php/vendor/react/socket/tests/SecureIntegrationTest.php new file mode 100644 index 0000000..8c9ba14 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/SecureIntegrationTest.php @@ -0,0 +1,204 @@ +markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $this->loop = LoopFactory::create(); + $this->server = new TcpServer(0, $this->loop); + $this->server = new SecureServer($this->server, $this->loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + $this->address = $this->server->getAddress(); + $this->connector = new SecureConnector(new TcpConnector($this->loop), $this->loop, array('verify_peer' => false)); + } + + public function tearDown() + { + if ($this->server !== null) { + $this->server->close(); + $this->server = null; + } + } + + public function testConnectToServer() + { + $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + $client->close(); + + // if we reach this, then everything is good + $this->assertNull(null); + } + + public function testConnectToServerEmitsConnection() + { + $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce()); + + $promiseClient = $this->connector->connect($this->address); + + list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + $client->close(); + } + + public function testSendSmallDataToServerReceivesOneChunk() + { + // server expects one connection which emits one data event + $received = new Deferred(); + $this->server->on('connection', function (ConnectionInterface $peer) use ($received) { + $peer->on('data', function ($chunk) use ($received) { + $received->resolve($chunk); + }); + }); + + $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + $client->write('hello'); + + // await server to report one "data" event + $data = Block\await($received->promise(), $this->loop, self::TIMEOUT); + + $client->close(); + + $this->assertEquals('hello', $data); + } + + public function testSendDataWithEndToServerReceivesAllData() + { + $disconnected = new Deferred(); + $this->server->on('connection', function (ConnectionInterface $peer) use ($disconnected) { + $received = ''; + $peer->on('data', function ($chunk) use (&$received) { + $received .= $chunk; + }); + $peer->on('close', function () use (&$received, $disconnected) { + $disconnected->resolve($received); + }); + }); + + $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + $data = str_repeat('a', 200000); + $client->end($data); + + // await server to report connection "close" event + $received = Block\await($disconnected->promise(), $this->loop, self::TIMEOUT); + + $this->assertEquals($data, $received); + } + + public function testSendDataWithoutEndingToServerReceivesAllData() + { + $received = ''; + $this->server->on('connection', function (ConnectionInterface $peer) use (&$received) { + $peer->on('data', function ($chunk) use (&$received) { + $received .= $chunk; + }); + }); + + $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + $data = str_repeat('d', 200000); + $client->write($data); + + // buffer incoming data for 0.1s (should be plenty of time) + Block\sleep(0.1, $this->loop); + + $client->close(); + + $this->assertEquals($data, $received); + } + + public function testConnectToServerWhichSendsSmallDataReceivesOneChunk() + { + $this->server->on('connection', function (ConnectionInterface $peer) { + $peer->write('hello'); + }); + + $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + // await client to report one "data" event + $receive = $this->createPromiseForEvent($client, 'data', $this->expectCallableOnceWith('hello')); + Block\await($receive, $this->loop, self::TIMEOUT); + + $client->close(); + } + + public function testConnectToServerWhichSendsDataWithEndReceivesAllData() + { + $data = str_repeat('b', 100000); + $this->server->on('connection', function (ConnectionInterface $peer) use ($data) { + $peer->end($data); + }); + + $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + // await data from client until it closes + $received = $this->buffer($client, $this->loop, self::TIMEOUT); + + $this->assertEquals($data, $received); + } + + public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData() + { + $data = str_repeat('c', 100000); + $this->server->on('connection', function (ConnectionInterface $peer) use ($data) { + $peer->write($data); + }); + + $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT); + /* @var $client ConnectionInterface */ + + // buffer incoming data for 0.1s (should be plenty of time) + $received = ''; + $client->on('data', function ($chunk) use (&$received) { + $received .= $chunk; + }); + Block\sleep(0.1, $this->loop); + + $client->close(); + + $this->assertEquals($data, $received); + } + + private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $fn) + { + return new Promise(function ($resolve) use ($emitter, $event, $fn) { + $emitter->on($event, function () use ($resolve, $fn) { + $resolve(call_user_func_array($fn, func_get_args())); + }); + }); + } +} diff --git a/assets/php/vendor/react/socket/tests/SecureServerTest.php b/assets/php/vendor/react/socket/tests/SecureServerTest.php new file mode 100644 index 0000000..92c641f --- /dev/null +++ b/assets/php/vendor/react/socket/tests/SecureServerTest.php @@ -0,0 +1,105 @@ +markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + } + + public function testGetAddressWillBePassedThroughToTcpServer() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('getAddress')->willReturn('tcp://127.0.0.1:1234'); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $server = new SecureServer($tcp, $loop, array()); + + $this->assertEquals('tls://127.0.0.1:1234', $server->getAddress()); + } + + public function testGetAddressWillReturnNullIfTcpServerReturnsNull() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('getAddress')->willReturn(null); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $server = new SecureServer($tcp, $loop, array()); + + $this->assertNull($server->getAddress()); + } + + public function testPauseWillBePassedThroughToTcpServer() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('pause'); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $server = new SecureServer($tcp, $loop, array()); + + $server->pause(); + } + + public function testResumeWillBePassedThroughToTcpServer() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('resume'); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $server = new SecureServer($tcp, $loop, array()); + + $server->resume(); + } + + public function testCloseWillBePassedThroughToTcpServer() + { + $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock(); + $tcp->expects($this->once())->method('close'); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $server = new SecureServer($tcp, $loop, array()); + + $server->close(); + } + + public function testConnectionWillBeEndedWithErrorIfItIsNotAStream() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $tcp = new TcpServer(0, $loop); + + $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); + $connection->expects($this->once())->method('end'); + + $server = new SecureServer($tcp, $loop, array()); + + $server->on('error', $this->expectCallableOnce()); + + $tcp->emit('connection', array($connection)); + } + + public function testSocketErrorWillBeForwarded() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $tcp = new TcpServer(0, $loop); + + $server = new SecureServer($tcp, $loop, array()); + + $server->on('error', $this->expectCallableOnce()); + + $tcp->emit('error', array(new \RuntimeException('test'))); + } +} diff --git a/assets/php/vendor/react/socket/tests/ServerTest.php b/assets/php/vendor/react/socket/tests/ServerTest.php new file mode 100644 index 0000000..14fdb2c --- /dev/null +++ b/assets/php/vendor/react/socket/tests/ServerTest.php @@ -0,0 +1,173 @@ +assertNotEquals(0, $server->getAddress()); + $server->close(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsForInvalidUri() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $server = new Server('invalid URI', $loop); + } + + public function testConstructorCreatesExpectedTcpServer() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + + $connector = new TcpConnector($loop); + $connector->connect($server->getAddress()) + ->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT); + + $connection->close(); + $server->close(); + } + + public function testConstructorCreatesExpectedUnixServer() + { + $loop = Factory::create(); + + $server = new Server($this->getRandomSocketUri(), $loop); + + $connector = new UnixConnector($loop); + $connector->connect($server->getAddress()) + ->then($this->expectCallableOnce(), $this->expectCallableNever()); + + $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT); + + $connection->close(); + $server->close(); + } + + public function testEmitsConnectionForNewConnection() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + $server->on('connection', $this->expectCallableOnce()); + + $client = stream_socket_client($server->getAddress()); + + Block\sleep(0.1, $loop); + } + + public function testDoesNotEmitConnectionForNewConnectionToPausedServer() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + $server->pause(); + $server->on('connection', $this->expectCallableNever()); + + $client = stream_socket_client($server->getAddress()); + + Block\sleep(0.1, $loop); + } + + public function testDoesEmitConnectionForNewConnectionToResumedServer() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + $server->pause(); + $server->on('connection', $this->expectCallableOnce()); + + $client = stream_socket_client($server->getAddress()); + + Block\sleep(0.1, $loop); + + $server->resume(); + Block\sleep(0.1, $loop); + } + + public function testDoesNotAllowConnectionToClosedServer() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + $server->on('connection', $this->expectCallableNever()); + $address = $server->getAddress(); + $server->close(); + + $client = @stream_socket_client($address); + + Block\sleep(0.1, $loop); + + $this->assertFalse($client); + } + + public function testEmitsConnectionWithInheritedContextOptions() + { + if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) { + // https://3v4l.org/hB4Tc + $this->markTestSkipped('Not supported on legacy HHVM < 3.13'); + } + + $loop = Factory::create(); + + $server = new Server(0, $loop, array( + 'backlog' => 4 + )); + + $all = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$all) { + $all = stream_context_get_options($conn->stream); + }); + + $client = stream_socket_client($server->getAddress()); + + Block\sleep(0.1, $loop); + + $this->assertEquals(array('socket' => array('backlog' => 4)), $all); + } + + public function testDoesNotEmitSecureConnectionForNewPlainConnection() + { + if (!function_exists('stream_socket_enable_crypto')) { + $this->markTestSkipped('Not supported on your platform (outdated HHVM?)'); + } + + $loop = Factory::create(); + + $server = new Server('tls://127.0.0.1:0', $loop, array( + 'tls' => array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + ) + )); + $server->on('connection', $this->expectCallableNever()); + + $client = stream_socket_client(str_replace('tls://', '', $server->getAddress())); + + Block\sleep(0.1, $loop); + } + + private function getRandomSocketUri() + { + return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock'; + } +} diff --git a/assets/php/vendor/react/socket/tests/Stub/CallableStub.php b/assets/php/vendor/react/socket/tests/Stub/CallableStub.php new file mode 100644 index 0000000..1b197eb --- /dev/null +++ b/assets/php/vendor/react/socket/tests/Stub/CallableStub.php @@ -0,0 +1,10 @@ +data .= $data; + + return true; + } + + public function end($data = null) + { + } + + public function close() + { + } + + public function getData() + { + return $this->data; + } + + public function getRemoteAddress() + { + return '127.0.0.1'; + } +} diff --git a/assets/php/vendor/react/socket/tests/Stub/ServerStub.php b/assets/php/vendor/react/socket/tests/Stub/ServerStub.php new file mode 100644 index 0000000..d9e74f4 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/Stub/ServerStub.php @@ -0,0 +1,18 @@ +connect('127.0.0.1:9999') + ->then($this->expectCallableNever(), $this->expectCallableOnce()); + + $loop->run(); + } + + /** @test */ + public function connectionToTcpServerShouldAddResourceToLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $connector = new TcpConnector($loop); + + $server = new TcpServer(0, $loop); + + $valid = false; + $loop->expects($this->once())->method('addWriteStream')->with($this->callback(function ($arg) use (&$valid) { + $valid = is_resource($arg); + return true; + })); + $connector->connect($server->getAddress()); + + $this->assertTrue($valid); + } + + /** @test */ + public function connectionToTcpServerShouldSucceed() + { + $loop = Factory::create(); + + $server = new TcpServer(9999, $loop); + $server->on('connection', $this->expectCallableOnce()); + $server->on('connection', array($server, 'close')); + + $connector = new TcpConnector($loop); + + $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT); + + $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection); + + $connection->close(); + } + + /** @test */ + public function connectionToTcpServerShouldSucceedWithRemoteAdressSameAsTarget() + { + $loop = Factory::create(); + + $server = new TcpServer(9999, $loop); + $server->on('connection', array($server, 'close')); + + $connector = new TcpConnector($loop); + + $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT); + /* @var $connection ConnectionInterface */ + + $this->assertEquals('tcp://127.0.0.1:9999', $connection->getRemoteAddress()); + + $connection->close(); + } + + /** @test */ + public function connectionToTcpServerShouldSucceedWithLocalAdressOnLocalhost() + { + $loop = Factory::create(); + + $server = new TcpServer(9999, $loop); + $server->on('connection', array($server, 'close')); + + $connector = new TcpConnector($loop); + + $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT); + /* @var $connection ConnectionInterface */ + + $this->assertContains('tcp://127.0.0.1:', $connection->getLocalAddress()); + $this->assertNotEquals('tcp://127.0.0.1:9999', $connection->getLocalAddress()); + + $connection->close(); + } + + /** @test */ + public function connectionToTcpServerShouldSucceedWithNullAddressesAfterConnectionClosed() + { + $loop = Factory::create(); + + $server = new TcpServer(9999, $loop); + $server->on('connection', array($server, 'close')); + + $connector = new TcpConnector($loop); + + $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT); + /* @var $connection ConnectionInterface */ + + $connection->close(); + + $this->assertNull($connection->getRemoteAddress()); + $this->assertNull($connection->getLocalAddress()); + } + + /** @test */ + public function connectionToTcpServerWillCloseWhenOtherSideCloses() + { + $loop = Factory::create(); + + // immediately close connection and server once connection is in + $server = new TcpServer(0, $loop); + $server->on('connection', function (ConnectionInterface $conn) use ($server) { + $conn->close(); + $server->close(); + }); + + $once = $this->expectCallableOnce(); + $connector = new TcpConnector($loop); + $connector->connect($server->getAddress())->then(function (ConnectionInterface $conn) use ($once) { + $conn->write('hello'); + $conn->on('close', $once); + }); + + $loop->run(); + } + + /** @test */ + public function connectionToEmptyIp6PortShouldFail() + { + $loop = Factory::create(); + + $connector = new TcpConnector($loop); + $connector + ->connect('[::1]:9999') + ->then($this->expectCallableNever(), $this->expectCallableOnce()); + + $loop->run(); + } + + /** @test */ + public function connectionToIp6TcpServerShouldSucceed() + { + $loop = Factory::create(); + + try { + $server = new TcpServer('[::1]:9999', $loop); + } catch (\Exception $e) { + $this->markTestSkipped('Unable to start IPv6 server socket (IPv6 not supported on this system?)'); + } + + $server->on('connection', $this->expectCallableOnce()); + $server->on('connection', array($server, 'close')); + + $connector = new TcpConnector($loop); + + $connection = Block\await($connector->connect('[::1]:9999'), $loop, self::TIMEOUT); + /* @var $connection ConnectionInterface */ + + $this->assertEquals('tcp://[::1]:9999', $connection->getRemoteAddress()); + + $this->assertContains('tcp://[::1]:', $connection->getLocalAddress()); + $this->assertNotEquals('tcp://[::1]:9999', $connection->getLocalAddress()); + + $connection->close(); + } + + /** @test */ + public function connectionToHostnameShouldFailImmediately() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $connector = new TcpConnector($loop); + $connector->connect('www.google.com:80')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } + + /** @test */ + public function connectionToInvalidPortShouldFailImmediately() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $connector = new TcpConnector($loop); + $connector->connect('255.255.255.255:12345678')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } + + /** @test */ + public function connectionToInvalidSchemeShouldFailImmediately() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $connector = new TcpConnector($loop); + $connector->connect('tls://google.com:443')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } + + /** @test */ + public function cancellingConnectionShouldRemoveResourceFromLoopAndCloseResource() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $connector = new TcpConnector($loop); + + $server = new TcpServer(0, $loop); + + $loop->expects($this->once())->method('addWriteStream'); + $promise = $connector->connect($server->getAddress()); + + $resource = null; + $valid = false; + $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($arg) use (&$resource, &$valid) { + $resource = $arg; + $valid = is_resource($arg); + return true; + })); + $promise->cancel(); + + $this->assertTrue($valid); + $this->assertFalse(is_resource($resource)); + } + + /** @test */ + public function cancellingConnectionShouldRejectPromise() + { + $loop = Factory::create(); + $connector = new TcpConnector($loop); + + $server = new TcpServer(0, $loop); + + $promise = $connector->connect($server->getAddress()); + $promise->cancel(); + + $this->setExpectedException('RuntimeException', 'Cancelled'); + Block\await($promise, $loop); + } +} diff --git a/assets/php/vendor/react/socket/tests/TcpServerTest.php b/assets/php/vendor/react/socket/tests/TcpServerTest.php new file mode 100644 index 0000000..72b3c28 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/TcpServerTest.php @@ -0,0 +1,285 @@ +loop = $this->createLoop(); + $this->server = new TcpServer(0, $this->loop); + + $this->port = parse_url($this->server->getAddress(), PHP_URL_PORT); + } + + /** + * @covers React\Socket\TcpServer::handleConnection + */ + public function testConnection() + { + $client = stream_socket_client('tcp://localhost:'.$this->port); + + $this->server->on('connection', $this->expectCallableOnce()); + + $this->tick(); + } + + /** + * @covers React\Socket\TcpServer::handleConnection + */ + public function testConnectionWithManyClients() + { + $client1 = stream_socket_client('tcp://localhost:'.$this->port); + $client2 = stream_socket_client('tcp://localhost:'.$this->port); + $client3 = stream_socket_client('tcp://localhost:'.$this->port); + + $this->server->on('connection', $this->expectCallableExactly(3)); + $this->tick(); + $this->tick(); + $this->tick(); + } + + public function testDataEventWillNotBeEmittedWhenClientSendsNoData() + { + $client = stream_socket_client('tcp://localhost:'.$this->port); + + $mock = $this->expectCallableNever(); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('data', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testDataWillBeEmittedWithDataClientSends() + { + $client = stream_socket_client('tcp://localhost:'.$this->port); + + fwrite($client, "foo\n"); + + $mock = $this->expectCallableOnceWith("foo\n"); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('data', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending() + { + $client = stream_socket_client('tcp://localhost:' . $this->port); + fwrite($client, "foo\n"); + stream_socket_shutdown($client, STREAM_SHUT_WR); + + $mock = $this->expectCallableOnceWith("foo\n"); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('data', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testLoopWillEndWhenServerIsClosed() + { + // explicitly unset server because we already call close() + $this->server->close(); + $this->server = null; + + $this->loop->run(); + + // if we reach this, then everything is good + $this->assertNull(null); + } + + public function testCloseTwiceIsNoOp() + { + $this->server->close(); + $this->server->close(); + + // if we reach this, then everything is good + $this->assertNull(null); + } + + public function testGetAddressAfterCloseReturnsNull() + { + $this->server->close(); + $this->assertNull($this->server->getAddress()); + } + + public function testLoopWillEndWhenServerIsClosedAfterSingleConnection() + { + $client = stream_socket_client('tcp://localhost:' . $this->port); + + // explicitly unset server because we only accept a single connection + // and then already call close() + $server = $this->server; + $this->server = null; + + $server->on('connection', function ($conn) use ($server) { + $conn->close(); + $server->close(); + }); + + $this->loop->run(); + + // if we reach this, then everything is good + $this->assertNull(null); + } + + public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts() + { + $client = stream_socket_client('tcp://localhost:' . $this->port); + $stream = new DuplexResourceStream($client, $this->loop); + + $bytes = 1024 * 1024; + $stream->end(str_repeat('*', $bytes)); + + $mock = $this->expectCallableOnce(); + + // explicitly unset server because we only accept a single connection + // and then already call close() + $server = $this->server; + $this->server = null; + + $received = 0; + $server->on('connection', function ($conn) use ($mock, &$received, $server) { + // count number of bytes received + $conn->on('data', function ($data) use (&$received) { + $received += strlen($data); + }); + + $conn->on('end', $mock); + + // do not await any further connections in order to let the loop terminate + $server->close(); + }); + + $this->loop->run(); + + $this->assertEquals($bytes, $received); + } + + public function testConnectionDoesNotEndWhenClientDoesNotClose() + { + $client = stream_socket_client('tcp://localhost:'.$this->port); + + $mock = $this->expectCallableNever(); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('end', $mock); + }); + $this->tick(); + $this->tick(); + } + + /** + * @covers React\Socket\Connection::end + */ + public function testConnectionDoesEndWhenClientCloses() + { + $client = stream_socket_client('tcp://localhost:'.$this->port); + + fclose($client); + + $mock = $this->expectCallableOnce(); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('end', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testCtorAddsResourceToLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('addReadStream'); + + $server = new TcpServer(0, $loop); + } + + public function testResumeWithoutPauseIsNoOp() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('addReadStream'); + + $server = new TcpServer(0, $loop); + $server->resume(); + } + + public function testPauseRemovesResourceFromLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('removeReadStream'); + + $server = new TcpServer(0, $loop); + $server->pause(); + } + + public function testPauseAfterPauseIsNoOp() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('removeReadStream'); + + $server = new TcpServer(0, $loop); + $server->pause(); + $server->pause(); + } + + public function testCloseRemovesResourceFromLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('removeReadStream'); + + $server = new TcpServer(0, $loop); + $server->close(); + } + + /** + * @expectedException RuntimeException + */ + public function testListenOnBusyPortThrows() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Windows supports listening on same port multiple times'); + } + + $another = new TcpServer($this->port, $this->loop); + } + + /** + * @covers React\Socket\TcpServer::close + */ + public function tearDown() + { + if ($this->server) { + $this->server->close(); + } + } + + private function tick() + { + Block\sleep(0, $this->loop); + } +} diff --git a/assets/php/vendor/react/socket/tests/TestCase.php b/assets/php/vendor/react/socket/tests/TestCase.php new file mode 100644 index 0000000..e87fc2f --- /dev/null +++ b/assets/php/vendor/react/socket/tests/TestCase.php @@ -0,0 +1,101 @@ +createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnceWith($value) + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($value); + + return $mock; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\Socket\Stub\CallableStub')->getMock(); + } + + protected function buffer(ReadableStreamInterface $stream, LoopInterface $loop, $timeout) + { + if (!$stream->isReadable()) { + return ''; + } + + return Block\await(new Promise( + function ($resolve, $reject) use ($stream) { + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $reject); + + $stream->on('close', function () use (&$buffer, $resolve) { + $resolve($buffer); + }); + }, + function () use ($stream) { + $stream->close(); + throw new \RuntimeException(); + } + ), $loop, $timeout); + } + + public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) + { + if (method_exists($this, 'expectException')) { + // PHPUnit 5+ + $this->expectException($exception); + if ($exceptionMessage !== '') { + $this->expectExceptionMessage($exceptionMessage); + } + if ($exceptionCode !== null) { + $this->expectExceptionCode($exceptionCode); + } + } else { + // legacy PHPUnit 4 + parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); + } + } +} diff --git a/assets/php/vendor/react/socket/tests/TimeoutConnectorTest.php b/assets/php/vendor/react/socket/tests/TimeoutConnectorTest.php new file mode 100644 index 0000000..64787d9 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/TimeoutConnectorTest.php @@ -0,0 +1,103 @@ +getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); + + $loop = Factory::create(); + + $timeout = new TimeoutConnector($connector, 0.01, $loop); + + $timeout->connect('google.com:80')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + + $loop->run(); + } + + public function testRejectsWhenConnectorRejects() + { + $promise = Promise\reject(new \RuntimeException()); + + $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); + + $loop = Factory::create(); + + $timeout = new TimeoutConnector($connector, 5.0, $loop); + + $timeout->connect('google.com:80')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + + $loop->run(); + } + + public function testResolvesWhenConnectorResolves() + { + $promise = Promise\resolve(); + + $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); + + $loop = Factory::create(); + + $timeout = new TimeoutConnector($connector, 5.0, $loop); + + $timeout->connect('google.com:80')->then( + $this->expectCallableOnce(), + $this->expectCallableNever() + ); + + $loop->run(); + } + + public function testRejectsAndCancelsPendingPromiseOnTimeout() + { + $promise = new Promise\Promise(function () { }, $this->expectCallableOnce()); + + $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); + + $loop = Factory::create(); + + $timeout = new TimeoutConnector($connector, 0.01, $loop); + + $timeout->connect('google.com:80')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + + $loop->run(); + } + + public function testCancelsPendingPromiseOnCancel() + { + $promise = new Promise\Promise(function () { }, function () { throw new \Exception(); }); + + $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); + + $loop = Factory::create(); + + $timeout = new TimeoutConnector($connector, 0.01, $loop); + + $out = $timeout->connect('google.com:80'); + $out->cancel(); + + $out->then($this->expectCallableNever(), $this->expectCallableOnce()); + } +} diff --git a/assets/php/vendor/react/socket/tests/UnixConnectorTest.php b/assets/php/vendor/react/socket/tests/UnixConnectorTest.php new file mode 100644 index 0000000..1564064 --- /dev/null +++ b/assets/php/vendor/react/socket/tests/UnixConnectorTest.php @@ -0,0 +1,64 @@ +loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $this->connector = new UnixConnector($this->loop); + } + + public function testInvalid() + { + $promise = $this->connector->connect('google.com:80'); + $promise->then(null, $this->expectCallableOnce()); + } + + public function testInvalidScheme() + { + $promise = $this->connector->connect('tcp://google.com:80'); + $promise->then(null, $this->expectCallableOnce()); + } + + public function testValid() + { + // random unix domain socket path + $path = sys_get_temp_dir() . '/test' . uniqid() . '.sock'; + + // temporarily create unix domain socket server to connect to + $server = stream_socket_server('unix://' . $path, $errno, $errstr); + + // skip test if we can not create a test server (Windows etc.) + if (!$server) { + $this->markTestSkipped('Unable to create socket "' . $path . '": ' . $errstr . '(' . $errno .')'); + return; + } + + // tests succeeds if we get notified of successful connection + $promise = $this->connector->connect($path); + $promise->then($this->expectCallableOnce()); + + // remember remote and local address of this connection and close again + $remote = $local = false; + $promise->then(function(ConnectionInterface $conn) use (&$remote, &$local) { + $remote = $conn->getRemoteAddress(); + $local = $conn->getLocalAddress(); + $conn->close(); + }); + + // clean up server + fclose($server); + unlink($path); + + $this->assertNull($local); + $this->assertEquals('unix://' . $path, $remote); + } +} diff --git a/assets/php/vendor/react/socket/tests/UnixServerTest.php b/assets/php/vendor/react/socket/tests/UnixServerTest.php new file mode 100644 index 0000000..10f7e4f --- /dev/null +++ b/assets/php/vendor/react/socket/tests/UnixServerTest.php @@ -0,0 +1,283 @@ +loop = Factory::create(); + $this->uds = $this->getRandomSocketUri(); + $this->server = new UnixServer($this->uds, $this->loop); + } + + /** + * @covers React\Socket\UnixServer::handleConnection + */ + public function testConnection() + { + $client = stream_socket_client($this->uds); + + $this->server->on('connection', $this->expectCallableOnce()); + $this->tick(); + } + + /** + * @covers React\Socket\UnixServer::handleConnection + */ + public function testConnectionWithManyClients() + { + $client1 = stream_socket_client($this->uds); + $client2 = stream_socket_client($this->uds); + $client3 = stream_socket_client($this->uds); + + $this->server->on('connection', $this->expectCallableExactly(3)); + $this->tick(); + $this->tick(); + $this->tick(); + } + + public function testDataEventWillNotBeEmittedWhenClientSendsNoData() + { + $client = stream_socket_client($this->uds); + + $mock = $this->expectCallableNever(); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('data', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testDataWillBeEmittedWithDataClientSends() + { + $client = stream_socket_client($this->uds); + + fwrite($client, "foo\n"); + + $mock = $this->expectCallableOnceWith("foo\n"); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('data', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending() + { + $client = stream_socket_client($this->uds); + fwrite($client, "foo\n"); + stream_socket_shutdown($client, STREAM_SHUT_WR); + + $mock = $this->expectCallableOnceWith("foo\n"); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('data', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testLoopWillEndWhenServerIsClosed() + { + // explicitly unset server because we already call close() + $this->server->close(); + $this->server = null; + + $this->loop->run(); + + // if we reach this, then everything is good + $this->assertNull(null); + } + + public function testCloseTwiceIsNoOp() + { + $this->server->close(); + $this->server->close(); + + // if we reach this, then everything is good + $this->assertNull(null); + } + + public function testGetAddressAfterCloseReturnsNull() + { + $this->server->close(); + $this->assertNull($this->server->getAddress()); + } + + public function testLoopWillEndWhenServerIsClosedAfterSingleConnection() + { + $client = stream_socket_client($this->uds); + + // explicitly unset server because we only accept a single connection + // and then already call close() + $server = $this->server; + $this->server = null; + + $server->on('connection', function ($conn) use ($server) { + $conn->close(); + $server->close(); + }); + + $this->loop->run(); + + // if we reach this, then everything is good + $this->assertNull(null); + } + + public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts() + { + $client = stream_socket_client($this->uds); + $stream = new DuplexResourceStream($client, $this->loop); + + $bytes = 1024 * 1024; + $stream->end(str_repeat('*', $bytes)); + + $mock = $this->expectCallableOnce(); + + // explicitly unset server because we only accept a single connection + // and then already call close() + $server = $this->server; + $this->server = null; + + $received = 0; + $server->on('connection', function ($conn) use ($mock, &$received, $server) { + // count number of bytes received + $conn->on('data', function ($data) use (&$received) { + $received += strlen($data); + }); + + $conn->on('end', $mock); + + // do not await any further connections in order to let the loop terminate + $server->close(); + }); + + $this->loop->run(); + + $this->assertEquals($bytes, $received); + } + + public function testConnectionDoesNotEndWhenClientDoesNotClose() + { + $client = stream_socket_client($this->uds); + + $mock = $this->expectCallableNever(); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('end', $mock); + }); + $this->tick(); + $this->tick(); + } + + /** + * @covers React\Socket\Connection::end + */ + public function testConnectionDoesEndWhenClientCloses() + { + $client = stream_socket_client($this->uds); + + fclose($client); + + $mock = $this->expectCallableOnce(); + + $this->server->on('connection', function ($conn) use ($mock) { + $conn->on('end', $mock); + }); + $this->tick(); + $this->tick(); + } + + public function testCtorAddsResourceToLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('addReadStream'); + + $server = new UnixServer($this->getRandomSocketUri(), $loop); + } + + public function testResumeWithoutPauseIsNoOp() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('addReadStream'); + + $server = new UnixServer($this->getRandomSocketUri(), $loop); + $server->resume(); + } + + public function testPauseRemovesResourceFromLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('removeReadStream'); + + $server = new UnixServer($this->getRandomSocketUri(), $loop); + $server->pause(); + } + + public function testPauseAfterPauseIsNoOp() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('removeReadStream'); + + $server = new UnixServer($this->getRandomSocketUri(), $loop); + $server->pause(); + $server->pause(); + } + + public function testCloseRemovesResourceFromLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->once())->method('removeReadStream'); + + $server = new UnixServer($this->getRandomSocketUri(), $loop); + $server->close(); + } + + /** + * @expectedException RuntimeException + */ + public function testListenOnBusyPortThrows() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Windows supports listening on same port multiple times'); + } + + $another = new UnixServer($this->uds, $this->loop); + } + + /** + * @covers React\Socket\UnixServer::close + */ + public function tearDown() + { + if ($this->server) { + $this->server->close(); + } + } + + private function getRandomSocketUri() + { + return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock'; + } + + private function tick() + { + Block\sleep(0, $this->loop); + } +} diff --git a/assets/php/vendor/react/stream/.gitignore b/assets/php/vendor/react/stream/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/assets/php/vendor/react/stream/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/assets/php/vendor/react/stream/.travis.yml b/assets/php/vendor/react/stream/.travis.yml new file mode 100644 index 0000000..f4e3376 --- /dev/null +++ b/assets/php/vendor/react/stream/.travis.yml @@ -0,0 +1,50 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7.0 +# - 7.0 # Mac OS X test setup, ignore errors, see below + - 7.1 + - 7.2 + - nightly # ignore errors, see below + - hhvm # ignore errors, see below + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + include: + - os: osx + language: generic + php: 7.0 # just to look right on travis + env: + - PACKAGE: php70 + allow_failures: + - php: nightly + - php: hhvm + - os: osx + +install: + # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP + - | + if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then + brew tap homebrew/homebrew-php + echo "Installing PHP ..." + brew install "${PACKAGE}" + brew install "${PACKAGE}"-xdebug + brew link "${PACKAGE}" + echo "Installing composer ..." + curl -s http://getcomposer.org/installer | php + mv composer.phar /usr/local/bin/composer + fi + - composer install --no-interaction + +script: + - vendor/bin/phpunit --coverage-text + - time php examples/91-benchmark-throughput.php diff --git a/assets/php/vendor/react/stream/CHANGELOG.md b/assets/php/vendor/react/stream/CHANGELOG.md new file mode 100644 index 0000000..f64815d --- /dev/null +++ b/assets/php/vendor/react/stream/CHANGELOG.md @@ -0,0 +1,377 @@ +# Changelog + +## 0.7.7 (2018-01-19) + +* Improve test suite by fixing forward compatibility with upcoming EventLoop + releases, avoid risky tests and add test group to skip integration tests + relying on internet connection and apply appropriate test timeouts. + (#128, #131 and #132 by @clue) + +## 0.7.6 (2017-12-21) + +* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12 + (#126 by @clue) + +* Improve test suite by simplifying test bootstrapping logic via Composer and + test against PHP 7.2 + (#127 by @clue and #124 by @carusogabriel) + +## 0.7.5 (2017-11-20) + +* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream` + (#119 by @clue) + +* Fix: Fix forward compatibility with upcoming EventLoop releases + (#121 by @clue) + +* Restructure examples to ease getting started + (#123 by @clue) + +* Improve test suite by adding forward compatibility with PHPUnit 6 and + ignore Mac OS X test failures for now until Travis tests work again + (#122 by @gabriel-caruso and #120 by @clue) + +## 0.7.4 (2017-10-11) + +* Fix: Remove event listeners from `CompositeStream` once closed and + remove undocumented left-over `close` event argument + (#116 by @clue) + +* Minor documentation improvements: Fix wrong class name in example, + fix typos in README and + fix forward compatibility with upcoming EventLoop releases in example + (#113 by @docteurklein and #114 and #115 by @clue) + +* Improve test suite by running against Mac OS X on Travis + (#112 by @clue) + +## 0.7.3 (2017-08-05) + +* Improvement: Support Événement 3.0 a long side 2.0 and 1.0 + (#108 by @WyriHaximus) + +* Readme: Corrected loop initialization in usage example + (#109 by @pulyavin) + +* Travis: Lock linux distribution preventing future builds from breaking + (#110 by @clue) + +## 0.7.2 (2017-06-15) + +* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream. + (#107 by @WyriHaximus) + +## 0.7.1 (2017-05-20) + +* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of + bytes to write at once. + (#105 by @clue) + + ```php + $stream = new WritableResourceStream(STDOUT, $loop, null, 8192); + ``` + +* Ignore HHVM test failures for now until Travis tests work again + (#106 by @clue) + +## 0.7.0 (2017-05-04) + +* Removed / BC break: Remove deprecated and unneeded functionality + (#45, #87, #90, #91 and #93 by @clue) + + * Remove deprecated `Stream` class, use `DuplexResourceStream` instead + (#87 by @clue) + + * Remove public `$buffer` property, use new constructor parameters instead + (#91 by @clue) + + * Remove public `$stream` property from all resource streams + (#90 by @clue) + + * Remove undocumented and now unused `ReadableStream` and `WritableStream` + (#93 by @clue) + + * Remove `BufferedSink` + (#45 by @clue) + +* Feature / BC break: Simplify `ThroughStream` by using data callback instead of + inheritance. It is now a direct implementation of `DuplexStreamInterface`. + (#88 and #89 by @clue) + + ```php + $through = new ThroughStream(function ($data) { + return json_encode($data) . PHP_EOL; + }); + $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); + + $through->write(array(2, true)); + ``` + +* Feature / BC break: The `CompositeStream` starts closed if either side is + already closed and forwards pause to pipe source on first write attempt. + (#96 and #103 by @clue) + + If either side of the composite stream closes, it will also close the other + side. We now also ensure that if either side is already closed during + instantiation, it will also close the other side. + +* BC break: Mark all classes as `final` and + mark internal API as `private` to discourage inheritance + (#95 and #99 by @clue) + +* Feature / BC break: Only emit `error` event for fatal errors + (#92 by @clue) + + > The `error` event was previously also allowed to be emitted for non-fatal + errors, but our implementations actually only ever emitted this as a fatal + error and then closed the stream. + +* Feature: Explicitly allow custom events and exclude any semantics + (#97 by @clue) + +* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation + (#100 and #102 by @clue) + +* Actually require all dependencies so this is self-contained and improve + forward compatibility with EventLoop v1.0 and v0.5 + (#94 and #98 by @clue) + +## 0.6.0 (2017-03-26) + +* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream` + (#85 by @clue) + + ```php + // old (does still work for BC reasons) + $stream = new Stream($connection, $loop); + + // new + $stream = new DuplexResourceStream($connection, $loop); + ``` + + Note that the `DuplexResourceStream` now rejects read-only or write-only + streams, so this may affect BC. If you want a read-only or write-only + resource, use `ReadableResourceStream` or `WritableResourceStream` instead of + `DuplexResourceStream`. + + > BC note: This class was previously called `Stream`. The `Stream` class still + exists for BC reasons and will be removed in future versions of this package. + +* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`) + (#84 by @clue) + + ```php + // old + $stream = new Buffer(STDOUT, $loop); + + // new + $stream = new WritableResourceStream(STDOUT, $loop); + ``` + +* Feature: Add `ReadableResourceStream` + (#83 by @clue) + + ```php + $stream = new ReadableResourceStream(STDIN, $loop); + ``` + +* Fix / BC Break: Enforce using non-blocking I/O + (#46 by @clue) + + > BC note: This is known to affect process pipes on Windows which do not + support non-blocking I/O and could thus block the whole EventLoop previously. + +* Feature / Fix / BC break: Consistent semantics for + `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side + (#86 by @clue) + +* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4 + (#80 by @clue) + +## 0.5.0 (2017-03-08) + +* Feature / BC break: Consistent `end` event semantics (EOF) + (#70 by @clue) + + The `end` event will now only be emitted for a *successful* end, not if the + stream closes due to an unrecoverable `error` event or if you call `close()` + explicitly. + If you want to detect when the stream closes (terminates), use the `close` + event instead. + +* BC break: Remove custom (undocumented) `full-drain` event from `Buffer` + (#63 and #68 by @clue) + + > The `full-drain` event was undocumented and mostly used internally. + Relying on this event has attracted some low-quality code in the past, so + we've removed this from the public API in order to work out a better + solution instead. + If you want to detect when the buffer finishes flushing data to the stream, + you may want to look into its `end()` method or the `close` event instead. + +* Feature / BC break: Consistent event semantics and documentation, + explicitly state *when* events will be emitted and *which* arguments they + receive. + (#73 and #69 by @clue) + + The documentation now explicitly defines each event and its arguments. + Custom events and event arguments are still supported. + Most notably, all defined events only receive inherently required event + arguments and no longer transmit the instance they are emitted on for + consistency and performance reasons. + + ```php + // old (inconsistent and not supported by all implementations) + $stream->on('data', function ($data, $stream) { + // process $data + }); + + // new (consistent throughout the whole ecosystem) + $stream->on('data', function ($data) use ($stream) { + // process $data + }); + ``` + + > This mostly adds documentation (and thus some stricter, consistent + definitions) for the existing behavior, it does NOT define any major + changes otherwise. + Most existing code should be compatible with these changes, unless + it relied on some undocumented/unintended semantics. + +* Feature / BC break: Consistent method semantics and documentation + (#72 by @clue) + + > This mostly adds documentation (and thus some stricter, consistent + definitions) for the existing behavior, it does NOT define any major + changes otherwise. + Most existing code should be compatible with these changes, unless + it relied on some undocumented/unintended semantics. + +* Feature: Consistent `pipe()` semantics for closed and closing streams + (#71 from @clue) + + The source stream will now always be paused via `pause()` when the + destination stream closes. Also, properly stop piping if the source + stream closes and remove all event forwarding. + +* Improve test suite by adding PHPUnit to `require-dev` and improving coverage. + (#74 and #75 by @clue, #66 by @nawarian) + +## 0.4.6 (2017-01-25) + +* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone) + (#62 by @clue) + +* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream` + (#60 by @clue) + +* Fix: Consistent `close` event behavior for `Buffer` + (#61 by @clue) + +## 0.4.5 (2016-11-13) + +* Feature: Support setting read buffer size to `null` (infinite) + (#42 by @clue) + +* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event + (#55 by @clue) + +* Vastly improved performance by factor of 10x to 20x. + Raise default buffer sizes to 64 KiB and simplify and improve error handling + and unneeded function calls. + (#53, #55, #56 by @clue) + +## 0.4.4 (2016-08-22) + +* Bug fix: Emit `error` event and close `Stream` when accessing the underlying + stream resource fails with a permanent error. + (#52 and #40 by @clue, #25 by @lysenkobv) + +* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF) + (#39 by @clue) + +* Bug fix: Ignore empty writes to `Buffer` + (#51 by @clue) + +* Add benchmarking script to measure throughput in CI + (#41 by @clue) + +## 0.4.3 (2015-10-07) + +* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau) +* Bug fix: No double-write during drain call (@arnaud-lb) +* Bug fix: Support HHVM (@clue) +* Adjust compatibility to 5.3 (@clue) + +## 0.4.2 (2014-09-09) + +* Added DuplexStreamInterface +* Stream sets stream resources to non-blocking +* Fixed potential race condition in pipe + +## 0.4.1 (2014-04-13) + +* Bug fix: v0.3.4 changes merged for v0.4.1 + +## 0.3.4 (2014-03-30) + +* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to Evenement 2.0 +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 + +## 0.3.3 (2013-07-08) + +* Bug fix: [Stream] Correctly detect closed connections + +## 0.3.2 (2013-05-10) + +* Bug fix: [Stream] Make sure CompositeStream is closed properly + +## 0.3.1 (2013-04-21) + +* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()` + +## 0.3.0 (2013-04-14) + +* Feature: [Stream] Factory method for BufferedSink + +## 0.2.6 (2012-12-26) + +* Version bump + +## 0.2.5 (2012-11-26) + +* Feature: Make BufferedSink trigger progress events on the promise (@jsor) + +## 0.2.4 (2012-11-18) + +* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream +* Feature: Added BufferedSink + +## 0.2.3 (2012-11-14) + +* Version bump + +## 0.2.2 (2012-10-28) + +* Version bump + +## 0.2.1 (2012-10-14) + +* Bug fix: Check for EOF in `Buffer::write()` + +## 0.2.0 (2012-09-10) + +* Version bump + +## 0.1.1 (2012-07-12) + +* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8 + +## 0.1.0 (2012-07-11) + +* First tagged release diff --git a/assets/php/vendor/react/stream/LICENSE b/assets/php/vendor/react/stream/LICENSE new file mode 100644 index 0000000..a808108 --- /dev/null +++ b/assets/php/vendor/react/stream/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Igor Wiedler, Chris Boden + +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/assets/php/vendor/react/stream/README.md b/assets/php/vendor/react/stream/README.md new file mode 100644 index 0000000..c362534 --- /dev/null +++ b/assets/php/vendor/react/stream/README.md @@ -0,0 +1,1224 @@ +# Stream + +[![Build Status](https://travis-ci.org/reactphp/stream.svg?branch=master)](https://travis-ci.org/reactphp/stream) + +Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). + +In order to make the [EventLoop](https://github.com/reactphp/event-loop) +easier to use, this component introduces the powerful concept of "streams". +Streams allow you to efficiently process huge amounts of data (such as a multi +Gigabyte file download) in small chunks without having to store everything in +memory at once. +They are very similar to the streams found in PHP itself, +but have an interface more suited for async, non-blocking I/O. + +**Table of contents** + +* [Stream usage](#stream-usage) + * [ReadableStreamInterface](#readablestreaminterface) + * [data event](#data-event) + * [end event](#end-event) + * [error event](#error-event) + * [close event](#close-event) + * [isReadable()](#isreadable) + * [pause()](#pause) + * [resume()](#resume) + * [pipe()](#pipe) + * [close()](#close) + * [WritableStreamInterface](#writablestreaminterface) + * [drain event](#drain-event) + * [pipe event](#pipe-event) + * [error event](#error-event-1) + * [close event](#close-event-1) + * [isWritable()](#iswritable) + * [write()](#write) + * [end()](#end) + * [close()](#close-1) + * [DuplexStreamInterface](#duplexstreaminterface) +* [Creating streams](#creating-streams) + * [ReadableResourceStream](#readableresourcestream) + * [WritableResourceStream](#writableresourcestream) + * [DuplexResourceStream](#duplexresourcestream) + * [ThroughStream](#throughstream) + * [CompositeStream](#compositestream) +* [Usage](#usage) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [More](#more) + +## Stream usage + +ReactPHP uses the concept of "streams" throughout its ecosystem to provide a +consistent higher-level abstraction for processing streams of arbitrary data +contents and size. +While a stream itself is a quite low-level concept, it can be used as a powerful +abstraction to build higher-level components and protocols on top. + +If you're new to this concept, it helps to think of them as a water pipe: +You can consume water from a source or you can produce water and forward (pipe) +it to any destination (sink). + +Similarly, streams can either be + +* readable (such as `STDIN` terminal input) or +* writable (such as `STDOUT` terminal output) or +* duplex (both readable *and* writable, such as a TCP/IP connection) + +Accordingly, this package defines the following three interfaces + +* [`ReadableStreamInterface`](#readablestreaminterface) +* [`WritableStreamInterface`](#writablestreaminterface) +* [`DuplexStreamInterface`](#duplexstreaminterface) + +### ReadableStreamInterface + +The `ReadableStreamInterface` is responsible for providing an interface for +read-only streams and the readable side of duplex streams. + +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to certain events. + +The event callback functions MUST be a valid `callable` that obeys strict +parameter definitions and MUST accept event parameters exactly as documented. +The event callback functions MUST NOT throw an `Exception`. +The return value of the event callback functions will be ignored and has no +effect, so for performance reasons you're recommended to not return any +excessive data structures. + +Every implementation of this interface MUST follow these event semantics in +order to be considered a well-behaving stream. + +> Note that higher-level implementations of this interface may choose to + define additional events with dedicated semantics not defined as part of + this low-level stream specification. Conformance with these event semantics + is out of scope for this interface, so you may also have to refer to the + documentation of such a higher-level implementation. + +#### data event + +The `data` event will be emitted whenever some data was read/received +from this source stream. +The event receives a single mixed argument for incoming data. + +```php +$stream->on('data', function ($data) { + echo $data; +}); +``` + +This event MAY be emitted any number of times, which may be zero times if +this stream does not send any data at all. +It SHOULD not be emitted after an `end` or `close` event. + +The given `$data` argument may be of mixed type, but it's usually +recommended it SHOULD be a `string` value or MAY use a type that allows +representation as a `string` for maximum compatibility. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will emit the raw (binary) payload data that is received over the wire as +chunks of `string` values. + +Due to the stream-based nature of this, the sender may send any number +of chunks with varying sizes. There are no guarantees that these chunks +will be received with the exact same framing the sender intended to send. +In other words, many lower-level protocols (such as TCP/IP) transfer the +data in chunks that may be anywhere between single-byte values to several +dozens of kilobytes. You may want to apply a higher-level protocol to +these low-level data chunks in order to achieve proper message framing. + +#### end event + +The `end` event will be emitted once the source stream has successfully +reached the end of the stream (EOF). + +```php +$stream->on('end', function () { + echo 'END'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +a successful end was detected. +It SHOULD NOT be emitted after a previous `end` or `close` event. +It MUST NOT be emitted if the stream closes due to a non-successful +end, such as after a previous `error` event. + +After the stream is ended, it MUST switch to non-readable mode, +see also `isReadable()`. + +This event will only be emitted if the *end* was reached successfully, +not if the stream was interrupted by an unrecoverable error or explicitly +closed. Not all streams know this concept of a "successful end". +Many use-cases involve detecting when the stream closes (terminates) +instead, in this case you should use the `close` event. +After the stream emits an `end` event, it SHOULD usually be followed by a +`close` event. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will emit this event if either the remote side closes the connection or +a file handle was successfully read until reaching its end (EOF). + +Note that this event should not be confused with the `end()` method. +This event defines a successful end *reading* from a source stream, while +the `end()` method defines *writing* a successful end to a destination +stream. + +#### error event + +The `error` event will be emitted once a fatal error occurs, usually while +trying to read from this stream. +The event receives a single `Exception` argument for the error instance. + +```php +$server->on('error', function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +This event SHOULD be emitted once the stream detects a fatal error, such +as a fatal transmission error or after an unexpected `data` or premature +`end` event. +It SHOULD NOT be emitted after a previous `error`, `end` or `close` event. +It MUST NOT be emitted if this is not a fatal error condition, such as +a temporary network issue that did not cause any data to be lost. + +After the stream errors, it MUST close the stream and SHOULD thus be +followed by a `close` event and then switch to non-readable mode, see +also `close()` and `isReadable()`. + +Many common streams (such as a TCP/IP connection or a file-based stream) +only deal with data transmission and do not make assumption about data +boundaries (such as unexpected `data` or premature `end` events). +In other words, many lower-level protocols (such as TCP/IP) may choose +to only emit this for a fatal transmission error once and will then +close (terminate) the stream in response. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements an `error` event. +In other words, an error may occur while either reading or writing the +stream which should result in the same error processing. + +#### close event + +The `close` event will be emitted once the stream closes (terminates). + +```php +$stream->on('close', function () { + echo 'CLOSED'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +the stream ever terminates. +It SHOULD NOT be emitted after a previous `close` event. + +After the stream is closed, it MUST switch to non-readable mode, +see also `isReadable()`. + +Unlike the `end` event, this event SHOULD be emitted whenever the stream +closes, irrespective of whether this happens implicitly due to an +unrecoverable error or explicitly when either side closes the stream. +If you only want to detect a *successful* end, you should use the `end` +event instead. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will likely choose to emit this event after reading a *successful* `end` +event or after a fatal transmission `error` event. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements a `close` event. +In other words, after receiving this event, the stream MUST switch into +non-writable AND non-readable mode, see also `isWritable()`. +Note that this event should not be confused with the `end` event. + +#### isReadable() + +The `isReadable(): bool` method can be used to +check whether this stream is in a readable state (not closed already). + +This method can be used to check if the stream still accepts incoming +data events or if it is ended or closed already. +Once the stream is non-readable, no further `data` or `end` events SHOULD +be emitted. + +```php +assert($stream->isReadable() === false); + +$stream->on('data', assertNeverCalled()); +$stream->on('end', assertNeverCalled()); +``` + +A successfully opened stream always MUST start in readable mode. + +Once the stream ends or closes, it MUST switch to non-readable mode. +This can happen any time, explicitly through `close()` or +implicitly due to a remote close or an unrecoverable transmission error. +Once a stream has switched to non-readable mode, it MUST NOT transition +back to readable mode. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements an `isWritable()` +method. Unless this is a half-open duplex stream, they SHOULD usually +have the same return value. + +#### pause() + +The `pause(): void` method can be used to +pause reading incoming data events. + +Removes the data source file descriptor from the event loop. This +allows you to throttle incoming data. + +Unless otherwise noted, a successfully opened stream SHOULD NOT start +in paused state. + +Once the stream is paused, no futher `data` or `end` events SHOULD +be emitted. + +```php +$stream->pause(); + +$stream->on('data', assertShouldNeverCalled()); +$stream->on('end', assertShouldNeverCalled()); +``` + +This method is advisory-only, though generally not recommended, the +stream MAY continue emitting `data` events. + +You can continue processing events by calling `resume()` again. + +Note that both methods can be called any number of times, in particular +calling `pause()` more than once SHOULD NOT have any effect. + +See also `resume()`. + +#### resume() + +The `resume(): void` method can be used to +resume reading incoming data events. + +Re-attach the data source after a previous `pause()`. + +```php +$stream->pause(); + +$loop->addTimer(1.0, function () use ($stream) { + $stream->resume(); +}); +``` + +Note that both methods can be called any number of times, in particular +calling `resume()` without a prior `pause()` SHOULD NOT have any effect. + +See also `pause()`. + +#### pipe() + +The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to +pipe all the data from this readable source into the given writable destination. + +Automatically sends all incoming data to the destination. +Automatically throttles the source based on what the destination can handle. + +```php +$source->pipe($dest); +``` + +Similarly, you can also pipe an instance implementing `DuplexStreamInterface` +into itself in order to write back all the data that is received. +This may be a useful feature for a TCP/IP echo service: + +```php +$connection->pipe($connection); +``` + +This method returns the destination stream as-is, which can be used to +set up chains of piped streams: + +```php +$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest); +``` + +By default, this will call `end()` on the destination stream once the +source stream emits an `end` event. This can be disabled like this: + +```php +$source->pipe($dest, array('end' => false)); +``` + +Note that this only applies to the `end` event. +If an `error` or explicit `close` event happens on the source stream, +you'll have to manually close the destination stream: + +```php +$source->pipe($dest); +$source->on('close', function () use ($dest) { + $dest->end('BYE!'); +}); +``` + +If the source stream is not readable (closed state), then this is a NO-OP. + +```php +$source->close(); +$source->pipe($dest); // NO-OP +``` + +If the destinantion stream is not writable (closed state), then this will simply +throttle (pause) the source stream: + +```php +$dest->close(); +$source->pipe($dest); // calls $source->pause() +``` + +Similarly, if the destination stream is closed while the pipe is still +active, it will also throttle (pause) the source stream: + +```php +$source->pipe($dest); +$dest->close(); // calls $source->pause() +``` + +Once the pipe is set up successfully, the destination stream MUST emit +a `pipe` event with this source stream an event argument. + +#### close() + +The `close(): void` method can be used to +close the stream (forcefully). + +This method can be used to (forcefully) close the stream. + +```php +$stream->close(); +``` + +Once the stream is closed, it SHOULD emit a `close` event. +Note that this event SHOULD NOT be emitted more than once, in particular +if this method is called multiple times. + +After calling this method, the stream MUST switch into a non-readable +mode, see also `isReadable()`. +This means that no further `data` or `end` events SHOULD be emitted. + +```php +$stream->close(); +assert($stream->isReadable() === false); + +$stream->on('data', assertNeverCalled()); +$stream->on('end', assertNeverCalled()); +``` + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements a `close()` method. +In other words, after calling this method, the stream MUST switch into +non-writable AND non-readable mode, see also `isWritable()`. +Note that this method should not be confused with the `end()` method. + +### WritableStreamInterface + +The `WritableStreamInterface` is responsible for providing an interface for +write-only streams and the writable side of duplex streams. + +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to certain events. + +The event callback functions MUST be a valid `callable` that obeys strict +parameter definitions and MUST accept event parameters exactly as documented. +The event callback functions MUST NOT throw an `Exception`. +The return value of the event callback functions will be ignored and has no +effect, so for performance reasons you're recommended to not return any +excessive data structures. + +Every implementation of this interface MUST follow these event semantics in +order to be considered a well-behaving stream. + +> Note that higher-level implementations of this interface may choose to + define additional events with dedicated semantics not defined as part of + this low-level stream specification. Conformance with these event semantics + is out of scope for this interface, so you may also have to refer to the + documentation of such a higher-level implementation. + +#### drain event + +The `drain` event will be emitted whenever the write buffer became full +previously and is now ready to accept more data. + +```php +$stream->on('drain', function () use ($stream) { + echo 'Stream is now ready to accept more data'; +}); +``` + +This event SHOULD be emitted once every time the buffer became full +previously and is now ready to accept more data. +In other words, this event MAY be emitted any number of times, which may +be zero times if the buffer never became full in the first place. +This event SHOULD NOT be emitted if the buffer has not become full +previously. + +This event is mostly used internally, see also `write()` for more details. + +#### pipe event + +The `pipe` event will be emitted whenever a readable stream is `pipe()`d +into this stream. +The event receives a single `ReadableStreamInterface` argument for the +source stream. + +```php +$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { + echo 'Now receiving piped data'; + + // explicitly close target if source emits an error + $source->on('error', function () use ($stream) { + $stream->close(); + }); +}); + +$source->pipe($stream); +``` + +This event MUST be emitted once for each readable stream that is +successfully piped into this destination stream. +In other words, this event MAY be emitted any number of times, which may +be zero times if no stream is ever piped into this stream. +This event MUST NOT be emitted if either the source is not readable +(closed already) or this destination is not writable (closed already). + +This event is mostly used internally, see also `pipe()` for more details. + +#### error event + +The `error` event will be emitted once a fatal error occurs, usually while +trying to write to this stream. +The event receives a single `Exception` argument for the error instance. + +```php +$stream->on('error', function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +This event SHOULD be emitted once the stream detects a fatal error, such +as a fatal transmission error. +It SHOULD NOT be emitted after a previous `error` or `close` event. +It MUST NOT be emitted if this is not a fatal error condition, such as +a temporary network issue that did not cause any data to be lost. + +After the stream errors, it MUST close the stream and SHOULD thus be +followed by a `close` event and then switch to non-writable mode, see +also `close()` and `isWritable()`. + +Many common streams (such as a TCP/IP connection or a file-based stream) +only deal with data transmission and may choose +to only emit this for a fatal transmission error once and will then +close (terminate) the stream in response. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements an `error` event. +In other words, an error may occur while either reading or writing the +stream which should result in the same error processing. + +#### close event + +The `close` event will be emitted once the stream closes (terminates). + +```php +$stream->on('close', function () { + echo 'CLOSED'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +the stream ever terminates. +It SHOULD NOT be emitted after a previous `close` event. + +After the stream is closed, it MUST switch to non-writable mode, +see also `isWritable()`. + +This event SHOULD be emitted whenever the stream closes, irrespective of +whether this happens implicitly due to an unrecoverable error or +explicitly when either side closes the stream. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will likely choose to emit this event after flushing the buffer from +the `end()` method, after receiving a *successful* `end` event or after +a fatal transmission `error` event. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements a `close` event. +In other words, after receiving this event, the stream MUST switch into +non-writable AND non-readable mode, see also `isReadable()`. +Note that this event should not be confused with the `end` event. + +#### isWritable() + +The `isWritable(): bool` method can be used to +check whether this stream is in a writable state (not closed already). + +This method can be used to check if the stream still accepts writing +any data or if it is ended or closed already. +Writing any data to a non-writable stream is a NO-OP: + +```php +assert($stream->isWritable() === false); + +$stream->write('end'); // NO-OP +$stream->end('end'); // NO-OP +``` + +A successfully opened stream always MUST start in writable mode. + +Once the stream ends or closes, it MUST switch to non-writable mode. +This can happen any time, explicitly through `end()` or `close()` or +implicitly due to a remote close or an unrecoverable transmission error. +Once a stream has switched to non-writable mode, it MUST NOT transition +back to writable mode. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements an `isReadable()` +method. Unless this is a half-open duplex stream, they SHOULD usually +have the same return value. + +#### write() + +The `write(mixed $data): bool` method can be used to +write some data into the stream. + +A successful write MUST be confirmed with a boolean `true`, which means +that either the data was written (flushed) immediately or is buffered and +scheduled for a future write. Note that this interface gives you no +control over explicitly flushing the buffered data, as finding the +appropriate time for this is beyond the scope of this interface and left +up to the implementation of this interface. + +Many common streams (such as a TCP/IP connection or file-based stream) +may choose to buffer all given data and schedule a future flush by using +an underlying EventLoop to check when the resource is actually writable. + +If a stream cannot handle writing (or flushing) the data, it SHOULD emit +an `error` event and MAY `close()` the stream if it can not recover from +this error. + +If the internal buffer is full after adding `$data`, then `write()` +SHOULD return `false`, indicating that the caller should stop sending +data until the buffer drains. +The stream SHOULD send a `drain` event once the buffer is ready to accept +more data. + +Similarly, if the the stream is not writable (already in a closed state) +it MUST NOT process the given `$data` and SHOULD return `false`, +indicating that the caller should stop sending data. + +The given `$data` argument MAY be of mixed type, but it's usually +recommended it SHOULD be a `string` value or MAY use a type that allows +representation as a `string` for maximum compatibility. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will only accept the raw (binary) payload data that is transferred over +the wire as chunks of `string` values. + +Due to the stream-based nature of this, the sender may send any number +of chunks with varying sizes. There are no guarantees that these chunks +will be received with the exact same framing the sender intended to send. +In other words, many lower-level protocols (such as TCP/IP) transfer the +data in chunks that may be anywhere between single-byte values to several +dozens of kilobytes. You may want to apply a higher-level protocol to +these low-level data chunks in order to achieve proper message framing. + +#### end() + +The `end(mixed $data = null): void` method can be used to +successfully end the stream (after optionally sending some final data). + +This method can be used to successfully end the stream, i.e. close +the stream after sending out all data that is currently buffered. + +```php +$stream->write('hello'); +$stream->write('world'); +$stream->end(); +``` + +If there's no data currently buffered and nothing to be flushed, then +this method MAY `close()` the stream immediately. + +If there's still data in the buffer that needs to be flushed first, then +this method SHOULD try to write out this data and only then `close()` +the stream. +Once the stream is closed, it SHOULD emit a `close` event. + +Note that this interface gives you no control over explicitly flushing +the buffered data, as finding the appropriate time for this is beyond the +scope of this interface and left up to the implementation of this +interface. + +Many common streams (such as a TCP/IP connection or file-based stream) +may choose to buffer all given data and schedule a future flush by using +an underlying EventLoop to check when the resource is actually writable. + +You can optionally pass some final data that is written to the stream +before ending the stream. If a non-`null` value is given as `$data`, then +this method will behave just like calling `write($data)` before ending +with no data. + +```php +// shorter version +$stream->end('bye'); + +// same as longer version +$stream->write('bye'); +$stream->end(); +``` + +After calling this method, the stream MUST switch into a non-writable +mode, see also `isWritable()`. +This means that no further writes are possible, so any additional +`write()` or `end()` calls have no effect. + +```php +$stream->end(); +assert($stream->isWritable() === false); + +$stream->write('nope'); // NO-OP +$stream->end(); // NO-OP +``` + +If this stream is a `DuplexStreamInterface`, calling this method SHOULD +also end its readable side, unless the stream supports half-open mode. +In other words, after calling this method, these streams SHOULD switch +into non-writable AND non-readable mode, see also `isReadable()`. +This implies that in this case, the stream SHOULD NOT emit any `data` +or `end` events anymore. +Streams MAY choose to use the `pause()` method logic for this, but +special care may have to be taken to ensure a following call to the +`resume()` method SHOULD NOT continue emitting readable events. + +Note that this method should not be confused with the `close()` method. + +#### close() + +The `close(): void` method can be used to +close the stream (forcefully). + +This method can be used to forcefully close the stream, i.e. close +the stream without waiting for any buffered data to be flushed. +If there's still data in the buffer, this data SHOULD be discarded. + +```php +$stream->close(); +``` + +Once the stream is closed, it SHOULD emit a `close` event. +Note that this event SHOULD NOT be emitted more than once, in particular +if this method is called multiple times. + +After calling this method, the stream MUST switch into a non-writable +mode, see also `isWritable()`. +This means that no further writes are possible, so any additional +`write()` or `end()` calls have no effect. + +```php +$stream->close(); +assert($stream->isWritable() === false); + +$stream->write('nope'); // NO-OP +$stream->end(); // NO-OP +``` + +Note that this method should not be confused with the `end()` method. +Unlike the `end()` method, this method does not take care of any existing +buffers and simply discards any buffer contents. +Likewise, this method may also be called after calling `end()` on a +stream in order to stop waiting for the stream to flush its final data. + +```php +$stream->end(); +$loop->addTimer(1.0, function () use ($stream) { + $stream->close(); +}); +``` + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements a `close()` method. +In other words, after calling this method, the stream MUST switch into +non-writable AND non-readable mode, see also `isReadable()`. + +### DuplexStreamInterface + +The `DuplexStreamInterface` is responsible for providing an interface for +duplex streams (both readable and writable). + +It builds on top of the existing interfaces for readable and writable streams +and follows the exact same method and event semantics. +If you're new to this concept, you should look into the +`ReadableStreamInterface` and `WritableStreamInterface` first. + +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to the same events defined +on the `ReadbleStreamInterface` and `WritableStreamInterface`. + +The event callback functions MUST be a valid `callable` that obeys strict +parameter definitions and MUST accept event parameters exactly as documented. +The event callback functions MUST NOT throw an `Exception`. +The return value of the event callback functions will be ignored and has no +effect, so for performance reasons you're recommended to not return any +excessive data structures. + +Every implementation of this interface MUST follow these event semantics in +order to be considered a well-behaving stream. + +> Note that higher-level implementations of this interface may choose to + define additional events with dedicated semantics not defined as part of + this low-level stream specification. Conformance with these event semantics + is out of scope for this interface, so you may also have to refer to the + documentation of such a higher-level implementation. + +See also [`ReadableStreamInterface`](#readablestreaminterface) and +[`WritableStreamInterface`](#writablestreaminterface) for more details. + +## Creating streams + +ReactPHP uses the concept of "streams" throughout its ecosystem, so that +many higher-level consumers of this package only deal with +[stream usage](#stream-usage). +This implies that stream instances are most often created within some +higher-level components and many consumers never actually have to deal with +creating a stream instance. + +* Use [react/socket](https://github.com/reactphp/socket) + if you want to accept incoming or establish outgoing plaintext TCP/IP or + secure TLS socket connection streams. +* Use [react/http](https://github.com/reactphp/http) + if you want to receive an incoming HTTP request body streams. +* Use [react/child-process](https://github.com/reactphp/child-process) + if you want to communicate with child processes via process pipes such as + STDIN, STDOUT, STDERR etc. +* Use experimental [react/filesystem](https://github.com/reactphp/filesystem) + if you want to read from / write to the filesystem. +* See also the last chapter for [more real-world applications](#more). + +However, if you are writing a lower-level component or want to create a stream +instance from a stream resource, then the following chapter is for you. + +> Note that the following examples use `fopen()` and `stream_socket_client()` + for illustration purposes only. + These functions SHOULD NOT be used in a truly async program because each call + may take several seconds to complete and would block the EventLoop otherwise. + Additionally, the `fopen()` call will return a file handle on some platforms + which may or may not be supported by all EventLoop implementations. + As an alternative, you may want to use higher-level libraries listed above. + +### ReadableResourceStream + +The `ReadableResourceStream` is a concrete implementation of the +[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources. + +This can be used to represent a read-only resource like a file stream opened in +readable mode or a stream such as `STDIN`: + +```php +$stream = new ReadableResourceStream(STDIN, $loop); +$stream->on('data', function ($chunk) { + echo $chunk; +}); +$stream->on('end', function () { + echo 'END'; +}); +``` + +See also [`ReadableStreamInterface`](#readablestreaminterface) for more details. + +The first parameter given to the constructor MUST be a valid stream resource +that is opened in reading mode (e.g. `fopen()` mode `r`). +Otherwise, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException +$stream = new ReadableResourceStream(false, $loop); +``` + +See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write +stream resources otherwise. + +Internally, this class tries to enable non-blocking mode on the stream resource +which may not be supported for all stream resources. +Most notably, this is not supported by pipes on Windows (STDIN etc.). +If this fails, it will throw a `RuntimeException`: + +```php +// throws RuntimeException on Windows +$stream = new ReadableResourceStream(STDIN, $loop); +``` + +Once the constructor is called with a valid stream resource, this class will +take care of the underlying stream resource. +You SHOULD only use its public API and SHOULD NOT interfere with the underlying +stream resource manually. + +This class takes an optional `int|null $readChunkSize` parameter that controls +the maximum buffer size in bytes to read at once from the stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. +This can be a positive number which means that up to X bytes will be read +at once from the underlying stream resource. Note that the actual number +of bytes read may be lower if the stream resource has less than X bytes +currently available. +This can be `-1` which means "read everything available" from the +underlying stream resource. +This should read until the stream resource is not readable anymore +(i.e. underlying buffer drained), note that this does not neccessarily +mean it reached EOF. + +```php +$stream = new ReadableResourceStream(STDIN, $loop, 8192); +``` + +> PHP bug warning: If the PHP process has explicitly been started without a + `STDIN` stream, then trying to read from `STDIN` may return data from + another stream resource. This does not happen if you start this with an empty + stream like `php test.php < /dev/null` instead of `php test.php <&-`. + See [#81](https://github.com/reactphp/stream/issues/81) for more details. + +### WritableResourceStream + +The `WritableResourceStream` is a concrete implementation of the +[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources. + +This can be used to represent a write-only resource like a file stream opened in +writable mode or a stream such as `STDOUT` or `STDERR`: + +```php +$stream = new WritableResourceStream(STDOUT, $loop); +$stream->write('hello!'); +$stream->end(); +``` + +See also [`WritableStreamInterface`](#writablestreaminterface) for more details. + +The first parameter given to the constructor MUST be a valid stream resource +that is opened for writing. +Otherwise, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException +$stream = new WritableResourceStream(false, $loop); +``` + +See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write +stream resources otherwise. + +Internally, this class tries to enable non-blocking mode on the stream resource +which may not be supported for all stream resources. +Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.). +If this fails, it will throw a `RuntimeException`: + +```php +// throws RuntimeException on Windows +$stream = new WritableResourceStream(STDOUT, $loop); +``` + +Once the constructor is called with a valid stream resource, this class will +take care of the underlying stream resource. +You SHOULD only use its public API and SHOULD NOT interfere with the underlying +stream resource manually. + +Any `write()` calls to this class will not be performed instantly, but will +be performed asynchronously, once the EventLoop reports the stream resource is +ready to accept data. +For this, it uses an in-memory buffer string to collect all outstanding writes. +This buffer has a soft-limit applied which defines how much data it is willing +to accept before the caller SHOULD stop sending further data. + +This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls +this maximum buffer size in bytes. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. + +```php +$stream = new WritableResourceStream(STDOUT, $loop, 8192); +``` + +This class takes an optional `int|null $writeChunkSize` parameter that controls +this maximum buffer size in bytes to write at once to the stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. +This can be a positive number which means that up to X bytes will be written +at once to the underlying stream resource. Note that the actual number +of bytes written may be lower if the stream resource has less than X bytes +currently available. +This can be `-1` which means "write everything available" to the +underlying stream resource. + +```php +$stream = new WritableResourceStream(STDOUT, $loop, null, 8192); +``` + +See also [`write()`](#write) for more details. + +### DuplexResourceStream + +The `DuplexResourceStream` is a concrete implementation of the +[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources. + +This can be used to represent a read-and-write resource like a file stream opened +in read and write mode mode or a stream such as a TCP/IP connection: + +```php +$conn = stream_socket_client('tcp://google.com:80'); +$stream = new DuplexResourceStream($conn, $loop); +$stream->write('hello!'); +$stream->end(); +``` + +See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details. + +The first parameter given to the constructor MUST be a valid stream resource +that is opened for reading *and* writing. +Otherwise, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException +$stream = new DuplexResourceStream(false, $loop); +``` + +See also the [`ReadableResourceStream`](#readableresourcestream) for read-only +and the [`WritableResourceStream`](#writableresourcestream) for write-only +stream resources otherwise. + +Internally, this class tries to enable non-blocking mode on the stream resource +which may not be supported for all stream resources. +Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.). +If this fails, it will throw a `RuntimeException`: + +```php +// throws RuntimeException on Windows +$stream = new DuplexResourceStream(STDOUT, $loop); +``` + +Once the constructor is called with a valid stream resource, this class will +take care of the underlying stream resource. +You SHOULD only use its public API and SHOULD NOT interfere with the underlying +stream resource manually. + +This class takes an optional `int|null $readChunkSize` parameter that controls +the maximum buffer size in bytes to read at once from the stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. +This can be a positive number which means that up to X bytes will be read +at once from the underlying stream resource. Note that the actual number +of bytes read may be lower if the stream resource has less than X bytes +currently available. +This can be `-1` which means "read everything available" from the +underlying stream resource. +This should read until the stream resource is not readable anymore +(i.e. underlying buffer drained), note that this does not neccessarily +mean it reached EOF. + +```php +$conn = stream_socket_client('tcp://google.com:80'); +$stream = new DuplexResourceStream($conn, $loop, 8192); +``` + +Any `write()` calls to this class will not be performed instantly, but will +be performed asynchronously, once the EventLoop reports the stream resource is +ready to accept data. +For this, it uses an in-memory buffer string to collect all outstanding writes. +This buffer has a soft-limit applied which defines how much data it is willing +to accept before the caller SHOULD stop sending further data. + +This class takes another optional `WritableStreamInterface|null $buffer` parameter +that controls this write behavior of this stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. + +If you want to change the write buffer soft limit, you can pass an instance of +[`WritableResourceStream`](#writableresourcestream) like this: + +```php +$conn = stream_socket_client('tcp://google.com:80'); +$buffer = new WritableResourceStream($conn, $loop, 8192); +$stream = new DuplexResourceStream($conn, $loop, null, $buffer); +``` + +See also [`WritableResourceStream`](#writableresourcestream) for more details. + +### ThroughStream + +The `ThroughStream` implements the +[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data +you write to it through to its readable end. + +```php +$through = new ThroughStream(); +$through->on('data', $this->expectCallableOnceWith('hello')); + +$through->write('hello'); +``` + +Similarly, the [`end()` method](#end) will end the stream and emit an +[`end` event](#end-event) and then [`close()`](#close-1) the stream. +The [`close()` method](#close-1) will close the stream and emit a +[`close` event](#close-event). +Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this: + +```php +$through = new ThroughStream(); +$source->pipe($through)->pipe($dest); +``` + +Optionally, its constructor accepts any callable function which will then be +used to *filter* any data written to it. This function receives a single data +argument as passed to the writable side and must return the data as it will be +passed to its readable end: + +```php +$through = new ThroughStream('strtoupper'); +$source->pipe($through)->pipe($dest); +``` + +Note that this class makes no assumptions about any data types. This can be +used to convert data, for example for transforming any structured data into +a newline-delimited JSON (NDJSON) stream like this: + +```php +$through = new ThroughStream(function ($data) { + return json_encode($data) . PHP_EOL; +}); +$through->on('data', $this->expectCallableOnceWith("[2, true]\n")); + +$through->write(array(2, true)); +``` + +The callback function is allowed to throw an `Exception`. In this case, +the stream will emit an `error` event and then [`close()`](#close-1) the stream. + +```php +$through = new ThroughStream(function ($data) { + if (!is_string($data)) { + throw new \UnexpectedValueException('Only strings allowed'); + } + return $data; +}); +$through->on('error', $this->expectCallableOnce())); +$through->on('close', $this->expectCallableOnce())); +$through->on('data', $this->expectCallableNever())); + +$through->write(2); +``` + +### CompositeStream + +The `CompositeStream` implements the +[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a +single duplex stream from two individual streams implementing +[`ReadableStreamInterface`](#readablestreaminterface) and +[`WritableStreamInterface`](#writablestreaminterface) respectively. + +This is useful for some APIs which may require a single +[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often +more convenient to work with a single stream instance like this: + +```php +$stdin = new ReadableResourceStream(STDIN, $loop); +$stdout = new WritableResourceStream(STDOUT, $loop); + +$stdio = new CompositeStream($stdin, $stdout); + +$stdio->on('data', function ($chunk) use ($stdio) { + $stdio->write('You said: ' . $chunk); +}); +``` + +This is a well-behaving stream which forwards all stream events from the +underlying streams and forwards all streams calls to the underlying streams. + +If you `write()` to the duplex stream, it will simply `write()` to the +writable side and return its status. + +If you `end()` the duplex stream, it will `end()` the writable side and will +`pause()` the readable side. + +If you `close()` the duplex stream, both input streams will be closed. +If either of the two input streams emits a `close` event, the duplex stream +will also close. +If either of the two input streams is already closed while constructing the +duplex stream, it will `close()` the other side and return a closed stream. + +## Usage + +The following example can be used to pipe the contents of a source file into +a destination file without having to ever read the whole file into memory: + +```php +$loop = new React\EventLoop\StreamSelectLoop; + +$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'), $loop); +$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'), $loop); + +$source->pipe($dest); + +$loop->run(); +``` + +> Note that this example uses `fopen()` for illustration purposes only. + This should not be used in a truly async program because the filesystem is + inherently blocking and each call could potentially take several seconds. + See also [creating streams](#creating-streams) for more sophisticated + examples. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require react/stream:^0.7.7 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM. +It's *highly recommended to use PHP 7+* for this project due to its vast +performance improvements. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +$ php vendor/bin/phpunit --exclude-group internet +``` + +## License + +MIT, see [LICENSE file](LICENSE). + +## More + +* See [creating streams](#creating-streams) for more information on how streams + are created in real-world applications. +* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the + [dependents on Packagist](https://packagist.org/packages/react/stream/dependents) + for a list of packages that use streams in real-world applications. diff --git a/assets/php/vendor/react/stream/composer.json b/assets/php/vendor/react/stream/composer.json new file mode 100644 index 0000000..f6faa66 --- /dev/null +++ b/assets/php/vendor/react/stream/composer.json @@ -0,0 +1,25 @@ +{ + "name": "react/stream", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"], + "license": "MIT", + "require": { + "php": ">=5.3.8", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35", + "clue/stream-filter": "~1.2" + }, + "autoload": { + "psr-4": { + "React\\Stream\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\Stream\\": "tests" + } + } +} diff --git a/assets/php/vendor/react/stream/examples/01-http.php b/assets/php/vendor/react/stream/examples/01-http.php new file mode 100644 index 0000000..3687f7c --- /dev/null +++ b/assets/php/vendor/react/stream/examples/01-http.php @@ -0,0 +1,40 @@ +on('data', function ($chunk) { + echo $chunk; +}); +$stream->on('close', function () { + echo '[CLOSED]' . PHP_EOL; +}); + +$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n"); + +$loop->run(); diff --git a/assets/php/vendor/react/stream/examples/02-https.php b/assets/php/vendor/react/stream/examples/02-https.php new file mode 100644 index 0000000..163f7c8 --- /dev/null +++ b/assets/php/vendor/react/stream/examples/02-https.php @@ -0,0 +1,40 @@ +on('data', function ($chunk) { + echo $chunk; +}); +$stream->on('close', function () { + echo '[CLOSED]' . PHP_EOL; +}); + +$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n"); + +$loop->run(); diff --git a/assets/php/vendor/react/stream/examples/11-cat.php b/assets/php/vendor/react/stream/examples/11-cat.php new file mode 100644 index 0000000..90fadc0 --- /dev/null +++ b/assets/php/vendor/react/stream/examples/11-cat.php @@ -0,0 +1,28 @@ +pipe($stdout); + +$loop->run(); diff --git a/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php b/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php new file mode 100644 index 0000000..ecf695c --- /dev/null +++ b/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php @@ -0,0 +1,62 @@ +write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL); +} +$info->write('piping from ' . $if . ' to ' . $of . ' (for max ' . $t . ' second(s)) ...'. PHP_EOL); + +// setup input and output streams and pipe inbetween +$fh = fopen($if, 'r'); +$in = new React\Stream\ReadableResourceStream($fh, $loop); +$out = new React\Stream\WritableResourceStream(fopen($of, 'w'), $loop); +$in->pipe($out); + +// stop input stream in $t seconds +$start = microtime(true); +$timeout = $loop->addTimer($t, function () use ($in, &$bytes) { + $in->close(); +}); + +// print stream position once stream closes +$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) { + $t = microtime(true) - $start; + $loop->cancelTimer($timeout); + + $bytes = ftell($fh); + + $info->write('read ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL); + $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL); +}); + +$loop->run(); diff --git a/assets/php/vendor/react/stream/phpunit.xml.dist b/assets/php/vendor/react/stream/phpunit.xml.dist new file mode 100644 index 0000000..13d3fab --- /dev/null +++ b/assets/php/vendor/react/stream/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/assets/php/vendor/react/stream/src/CompositeStream.php b/assets/php/vendor/react/stream/src/CompositeStream.php new file mode 100644 index 0000000..153f2a3 --- /dev/null +++ b/assets/php/vendor/react/stream/src/CompositeStream.php @@ -0,0 +1,82 @@ +readable = $readable; + $this->writable = $writable; + + if (!$readable->isReadable() || !$writable->isWritable()) { + return $this->close(); + } + + Util::forwardEvents($this->readable, $this, array('data', 'end', 'error')); + Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe')); + + $this->readable->on('close', array($this, 'close')); + $this->writable->on('close', array($this, 'close')); + } + + public function isReadable() + { + return $this->readable->isReadable(); + } + + public function pause() + { + $this->readable->pause(); + } + + public function resume() + { + if (!$this->writable->isWritable()) { + return; + } + + $this->readable->resume(); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + public function isWritable() + { + return $this->writable->isWritable(); + } + + public function write($data) + { + return $this->writable->write($data); + } + + public function end($data = null) + { + $this->readable->pause(); + $this->writable->end($data); + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->closed = true; + $this->readable->close(); + $this->writable->close(); + + $this->emit('close'); + $this->removeAllListeners(); + } +} diff --git a/assets/php/vendor/react/stream/src/DuplexResourceStream.php b/assets/php/vendor/react/stream/src/DuplexResourceStream.php new file mode 100644 index 0000000..982ebb0 --- /dev/null +++ b/assets/php/vendor/react/stream/src/DuplexResourceStream.php @@ -0,0 +1,224 @@ +isLegacyPipe($stream)) { + stream_set_read_buffer($stream, 0); + } + + if ($buffer === null) { + $buffer = new WritableResourceStream($stream, $loop); + } + + $this->stream = $stream; + $this->loop = $loop; + $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; + $this->buffer = $buffer; + + $that = $this; + + $this->buffer->on('error', function ($error) use ($that) { + $that->emit('error', array($error)); + }); + + $this->buffer->on('close', array($this, 'close')); + + $this->buffer->on('drain', function () use ($that) { + $that->emit('drain'); + }); + + $this->resume(); + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function pause() + { + if ($this->listening) { + $this->loop->removeReadStream($this->stream); + $this->listening = false; + } + } + + public function resume() + { + if (!$this->listening && $this->readable) { + $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->listening = true; + } + } + + public function write($data) + { + if (!$this->writable) { + return false; + } + + return $this->buffer->write($data); + } + + public function close() + { + if (!$this->writable && !$this->closing) { + return; + } + + $this->closing = false; + + $this->readable = false; + $this->writable = false; + + $this->emit('close'); + $this->pause(); + $this->buffer->close(); + $this->removeAllListeners(); + + if (is_resource($this->stream)) { + fclose($this->stream); + } + } + + public function end($data = null) + { + if (!$this->writable) { + return; + } + + $this->closing = true; + + $this->readable = false; + $this->writable = false; + $this->pause(); + + $this->buffer->end($data); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + /** @internal */ + public function handleData($stream) + { + $error = null; + set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + $error = new \ErrorException( + $errstr, + 0, + $errno, + $errfile, + $errline + ); + }); + + $data = stream_get_contents($stream, $this->bufferSize); + + restore_error_handler(); + + if ($error !== null) { + $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); + $this->close(); + return; + } + + if ($data !== '') { + $this->emit('data', array($data)); + } else{ + // no data read => we reached the end and close the stream + $this->emit('end'); + $this->close(); + } + } + + /** + * Returns whether this is a pipe resource in a legacy environment + * + * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+ + * and PHP 5.5.12+ and newer. + * + * @param resource $resource + * @return bool + * @link https://github.com/reactphp/child-process/issues/40 + * + * @codeCoverageIgnore + */ + private function isLegacyPipe($resource) + { + if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) { + $meta = stream_get_meta_data($resource); + + if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') { + return true; + } + } + return false; + } +} diff --git a/assets/php/vendor/react/stream/src/DuplexStreamInterface.php b/assets/php/vendor/react/stream/src/DuplexStreamInterface.php new file mode 100644 index 0000000..631ce31 --- /dev/null +++ b/assets/php/vendor/react/stream/src/DuplexStreamInterface.php @@ -0,0 +1,39 @@ + Note that higher-level implementations of this interface may choose to + * define additional events with dedicated semantics not defined as part of + * this low-level stream specification. Conformance with these event semantics + * is out of scope for this interface, so you may also have to refer to the + * documentation of such a higher-level implementation. + * + * @see ReadableStreamInterface + * @see WritableStreamInterface + */ +interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface +{ +} diff --git a/assets/php/vendor/react/stream/src/ReadableResourceStream.php b/assets/php/vendor/react/stream/src/ReadableResourceStream.php new file mode 100644 index 0000000..015a96b --- /dev/null +++ b/assets/php/vendor/react/stream/src/ReadableResourceStream.php @@ -0,0 +1,177 @@ +isLegacyPipe($stream)) { + stream_set_read_buffer($stream, 0); + } + + $this->stream = $stream; + $this->loop = $loop; + $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; + + $this->resume(); + } + + public function isReadable() + { + return !$this->closed; + } + + public function pause() + { + if ($this->listening) { + $this->loop->removeReadStream($this->stream); + $this->listening = false; + } + } + + public function resume() + { + if (!$this->listening && !$this->closed) { + $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->listening = true; + } + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->closed = true; + + $this->emit('close'); + $this->pause(); + $this->removeAllListeners(); + + if (is_resource($this->stream)) { + fclose($this->stream); + } + } + + /** @internal */ + public function handleData() + { + $error = null; + set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + $error = new \ErrorException( + $errstr, + 0, + $errno, + $errfile, + $errline + ); + }); + + $data = stream_get_contents($this->stream, $this->bufferSize); + + restore_error_handler(); + + if ($error !== null) { + $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); + $this->close(); + return; + } + + if ($data !== '') { + $this->emit('data', array($data)); + } else{ + // no data read => we reached the end and close the stream + $this->emit('end'); + $this->close(); + } + } + + /** + * Returns whether this is a pipe resource in a legacy environment + * + * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+ + * and PHP 5.5.12+ and newer. + * + * @param resource $resource + * @return bool + * @link https://github.com/reactphp/child-process/issues/40 + * + * @codeCoverageIgnore + */ + private function isLegacyPipe($resource) + { + if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) { + $meta = stream_get_meta_data($resource); + + if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') { + return true; + } + } + return false; + } +} diff --git a/assets/php/vendor/react/stream/src/ReadableStreamInterface.php b/assets/php/vendor/react/stream/src/ReadableStreamInterface.php new file mode 100644 index 0000000..2b4c3d0 --- /dev/null +++ b/assets/php/vendor/react/stream/src/ReadableStreamInterface.php @@ -0,0 +1,362 @@ +on('data', function ($data) { + * echo $data; + * }); + * ``` + * + * This event MAY be emitted any number of times, which may be zero times if + * this stream does not send any data at all. + * It SHOULD not be emitted after an `end` or `close` event. + * + * The given `$data` argument may be of mixed type, but it's usually + * recommended it SHOULD be a `string` value or MAY use a type that allows + * representation as a `string` for maximum compatibility. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will emit the raw (binary) payload data that is received over the wire as + * chunks of `string` values. + * + * Due to the stream-based nature of this, the sender may send any number + * of chunks with varying sizes. There are no guarantees that these chunks + * will be received with the exact same framing the sender intended to send. + * In other words, many lower-level protocols (such as TCP/IP) transfer the + * data in chunks that may be anywhere between single-byte values to several + * dozens of kilobytes. You may want to apply a higher-level protocol to + * these low-level data chunks in order to achieve proper message framing. + * + * end event: + * The `end` event will be emitted once the source stream has successfully + * reached the end of the stream (EOF). + * + * ```php + * $stream->on('end', function () { + * echo 'END'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * a successful end was detected. + * It SHOULD NOT be emitted after a previous `end` or `close` event. + * It MUST NOT be emitted if the stream closes due to a non-successful + * end, such as after a previous `error` event. + * + * After the stream is ended, it MUST switch to non-readable mode, + * see also `isReadable()`. + * + * This event will only be emitted if the *end* was reached successfully, + * not if the stream was interrupted by an unrecoverable error or explicitly + * closed. Not all streams know this concept of a "successful end". + * Many use-cases involve detecting when the stream closes (terminates) + * instead, in this case you should use the `close` event. + * After the stream emits an `end` event, it SHOULD usually be followed by a + * `close` event. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will emit this event if either the remote side closes the connection or + * a file handle was successfully read until reaching its end (EOF). + * + * Note that this event should not be confused with the `end()` method. + * This event defines a successful end *reading* from a source stream, while + * the `end()` method defines *writing* a successful end to a destination + * stream. + * + * error event: + * The `error` event will be emitted once a fatal error occurs, usually while + * trying to read from this stream. + * The event receives a single `Exception` argument for the error instance. + * + * ```php + * $stream->on('error', function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * This event SHOULD be emitted once the stream detects a fatal error, such + * as a fatal transmission error or after an unexpected `data` or premature + * `end` event. + * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event. + * It MUST NOT be emitted if this is not a fatal error condition, such as + * a temporary network issue that did not cause any data to be lost. + * + * After the stream errors, it MUST close the stream and SHOULD thus be + * followed by a `close` event and then switch to non-readable mode, see + * also `close()` and `isReadable()`. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * only deal with data transmission and do not make assumption about data + * boundaries (such as unexpected `data` or premature `end` events). + * In other words, many lower-level protocols (such as TCP/IP) may choose + * to only emit this for a fatal transmission error once and will then + * close (terminate) the stream in response. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements an `error` event. + * In other words, an error may occur while either reading or writing the + * stream which should result in the same error processing. + * + * close event: + * The `close` event will be emitted once the stream closes (terminates). + * + * ```php + * $stream->on('close', function () { + * echo 'CLOSED'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * the stream ever terminates. + * It SHOULD NOT be emitted after a previous `close` event. + * + * After the stream is closed, it MUST switch to non-readable mode, + * see also `isReadable()`. + * + * Unlike the `end` event, this event SHOULD be emitted whenever the stream + * closes, irrespective of whether this happens implicitly due to an + * unrecoverable error or explicitly when either side closes the stream. + * If you only want to detect a *successful* end, you should use the `end` + * event instead. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will likely choose to emit this event after reading a *successful* `end` + * event or after a fatal transmission `error` event. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements a `close` event. + * In other words, after receiving this event, the stream MUST switch into + * non-writable AND non-readable mode, see also `isWritable()`. + * Note that this event should not be confused with the `end` event. + * + * The event callback functions MUST be a valid `callable` that obeys strict + * parameter definitions and MUST accept event parameters exactly as documented. + * The event callback functions MUST NOT throw an `Exception`. + * The return value of the event callback functions will be ignored and has no + * effect, so for performance reasons you're recommended to not return any + * excessive data structures. + * + * Every implementation of this interface MUST follow these event semantics in + * order to be considered a well-behaving stream. + * + * > Note that higher-level implementations of this interface may choose to + * define additional events with dedicated semantics not defined as part of + * this low-level stream specification. Conformance with these event semantics + * is out of scope for this interface, so you may also have to refer to the + * documentation of such a higher-level implementation. + * + * @see EventEmitterInterface + */ +interface ReadableStreamInterface extends EventEmitterInterface +{ + /** + * Checks whether this stream is in a readable state (not closed already). + * + * This method can be used to check if the stream still accepts incoming + * data events or if it is ended or closed already. + * Once the stream is non-readable, no further `data` or `end` events SHOULD + * be emitted. + * + * ```php + * assert($stream->isReadable() === false); + * + * $stream->on('data', assertNeverCalled()); + * $stream->on('end', assertNeverCalled()); + * ``` + * + * A successfully opened stream always MUST start in readable mode. + * + * Once the stream ends or closes, it MUST switch to non-readable mode. + * This can happen any time, explicitly through `close()` or + * implicitly due to a remote close or an unrecoverable transmission error. + * Once a stream has switched to non-readable mode, it MUST NOT transition + * back to readable mode. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements an `isWritable()` + * method. Unless this is a half-open duplex stream, they SHOULD usually + * have the same return value. + * + * @return bool + */ + public function isReadable(); + + /** + * Pauses reading incoming data events. + * + * Removes the data source file descriptor from the event loop. This + * allows you to throttle incoming data. + * + * Unless otherwise noted, a successfully opened stream SHOULD NOT start + * in paused state. + * + * Once the stream is paused, no futher `data` or `end` events SHOULD + * be emitted. + * + * ```php + * $stream->pause(); + * + * $stream->on('data', assertShouldNeverCalled()); + * $stream->on('end', assertShouldNeverCalled()); + * ``` + * + * This method is advisory-only, though generally not recommended, the + * stream MAY continue emitting `data` events. + * + * You can continue processing events by calling `resume()` again. + * + * Note that both methods can be called any number of times, in particular + * calling `pause()` more than once SHOULD NOT have any effect. + * + * @see self::resume() + * @return void + */ + public function pause(); + + /** + * Resumes reading incoming data events. + * + * Re-attach the data source after a previous `pause()`. + * + * ```php + * $stream->pause(); + * + * $loop->addTimer(1.0, function () use ($stream) { + * $stream->resume(); + * }); + * ``` + * + * Note that both methods can be called any number of times, in particular + * calling `resume()` without a prior `pause()` SHOULD NOT have any effect. + * + * @see self::pause() + * @return void + */ + public function resume(); + + /** + * Pipes all the data from this readable source into the given writable destination. + * + * Automatically sends all incoming data to the destination. + * Automatically throttles the source based on what the destination can handle. + * + * ```php + * $source->pipe($dest); + * ``` + * + * Similarly, you can also pipe an instance implementing `DuplexStreamInterface` + * into itself in order to write back all the data that is received. + * This may be a useful feature for a TCP/IP echo service: + * + * ```php + * $connection->pipe($connection); + * ``` + * + * This method returns the destination stream as-is, which can be used to + * set up chains of piped streams: + * + * ```php + * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest); + * ``` + * + * By default, this will call `end()` on the destination stream once the + * source stream emits an `end` event. This can be disabled like this: + * + * ```php + * $source->pipe($dest, array('end' => false)); + * ``` + * + * Note that this only applies to the `end` event. + * If an `error` or explicit `close` event happens on the source stream, + * you'll have to manually close the destination stream: + * + * ```php + * $source->pipe($dest); + * $source->on('close', function () use ($dest) { + * $dest->end('BYE!'); + * }); + * ``` + * + * If the source stream is not readable (closed state), then this is a NO-OP. + * + * ```php + * $source->close(); + * $source->pipe($dest); // NO-OP + * ``` + * + * If the destinantion stream is not writable (closed state), then this will simply + * throttle (pause) the source stream: + * + * ```php + * $dest->close(); + * $source->pipe($dest); // calls $source->pause() + * ``` + * + * Similarly, if the destination stream is closed while the pipe is still + * active, it will also throttle (pause) the source stream: + * + * ```php + * $source->pipe($dest); + * $dest->close(); // calls $source->pause() + * ``` + * + * Once the pipe is set up successfully, the destination stream MUST emit + * a `pipe` event with this source stream an event argument. + * + * @param WritableStreamInterface $dest + * @param array $options + * @return WritableStreamInterface $dest stream as-is + */ + public function pipe(WritableStreamInterface $dest, array $options = array()); + + /** + * Closes the stream (forcefully). + * + * This method can be used to (forcefully) close the stream. + * + * ```php + * $stream->close(); + * ``` + * + * Once the stream is closed, it SHOULD emit a `close` event. + * Note that this event SHOULD NOT be emitted more than once, in particular + * if this method is called multiple times. + * + * After calling this method, the stream MUST switch into a non-readable + * mode, see also `isReadable()`. + * This means that no further `data` or `end` events SHOULD be emitted. + * + * ```php + * $stream->close(); + * assert($stream->isReadable() === false); + * + * $stream->on('data', assertNeverCalled()); + * $stream->on('end', assertNeverCalled()); + * ``` + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements a `close()` method. + * In other words, after calling this method, the stream MUST switch into + * non-writable AND non-readable mode, see also `isWritable()`. + * Note that this method should not be confused with the `end()` method. + * + * @return void + * @see WritableStreamInterface::close() + */ + public function close(); +} diff --git a/assets/php/vendor/react/stream/src/ThroughStream.php b/assets/php/vendor/react/stream/src/ThroughStream.php new file mode 100644 index 0000000..da2fbb0 --- /dev/null +++ b/assets/php/vendor/react/stream/src/ThroughStream.php @@ -0,0 +1,190 @@ +on('data', $this->expectCallableOnceWith('hello')); + * + * $through->write('hello'); + * ``` + * + * Similarly, the [`end()` method](#end) will end the stream and emit an + * [`end` event](#end-event) and then [`close()`](#close-1) the stream. + * The [`close()` method](#close-1) will close the stream and emit a + * [`close` event](#close-event). + * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this: + * + * ```php + * $through = new ThroughStream(); + * $source->pipe($through)->pipe($dest); + * ``` + * + * Optionally, its constructor accepts any callable function which will then be + * used to *filter* any data written to it. This function receives a single data + * argument as passed to the writable side and must return the data as it will be + * passed to its readable end: + * + * ```php + * $through = new ThroughStream('strtoupper'); + * $source->pipe($through)->pipe($dest); + * ``` + * + * Note that this class makes no assumptions about any data types. This can be + * used to convert data, for example for transforming any structured data into + * a newline-delimited JSON (NDJSON) stream like this: + * + * ```php + * $through = new ThroughStream(function ($data) { + * return json_encode($data) . PHP_EOL; + * }); + * $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); + * + * $through->write(array(2, true)); + * ``` + * + * The callback function is allowed to throw an `Exception`. In this case, + * the stream will emit an `error` event and then [`close()`](#close-1) the stream. + * + * ```php + * $through = new ThroughStream(function ($data) { + * if (!is_string($data)) { + * throw new \UnexpectedValueException('Only strings allowed'); + * } + * return $data; + * }); + * $through->on('error', $this->expectCallableOnce())); + * $through->on('close', $this->expectCallableOnce())); + * $through->on('data', $this->expectCallableNever())); + * + * $through->write(2); + * ``` + * + * @see WritableStreamInterface::write() + * @see WritableStreamInterface::end() + * @see DuplexStreamInterface::close() + * @see WritableStreamInterface::pipe() + */ +final class ThroughStream extends EventEmitter implements DuplexStreamInterface +{ + private $readable = true; + private $writable = true; + private $closed = false; + private $paused = false; + private $drain = false; + private $callback; + + public function __construct($callback = null) + { + if ($callback !== null && !is_callable($callback)) { + throw new InvalidArgumentException('Invalid transformation callback given'); + } + + $this->callback = $callback; + } + + public function pause() + { + $this->paused = true; + } + + public function resume() + { + if ($this->drain) { + $this->drain = false; + $this->emit('drain'); + } + $this->paused = false; + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function write($data) + { + if (!$this->writable) { + return false; + } + + if ($this->callback !== null) { + try { + $data = call_user_func($this->callback, $data); + } catch (\Exception $e) { + $this->emit('error', array($e)); + $this->close(); + + return false; + } + } + + $this->emit('data', array($data)); + + if ($this->paused) { + $this->drain = true; + return false; + } + + return true; + } + + public function end($data = null) + { + if (!$this->writable) { + return; + } + + if (null !== $data) { + $this->write($data); + + // return if write() already caused the stream to close + if (!$this->writable) { + return; + } + } + + $this->readable = false; + $this->writable = false; + $this->paused = true; + $this->drain = false; + + $this->emit('end'); + $this->close(); + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->readable = false; + $this->writable = false; + $this->closed = true; + $this->paused = true; + $this->drain = false; + $this->callback = null; + + $this->emit('close'); + $this->removeAllListeners(); + } +} diff --git a/assets/php/vendor/react/stream/src/Util.php b/assets/php/vendor/react/stream/src/Util.php new file mode 100644 index 0000000..14ddcfc --- /dev/null +++ b/assets/php/vendor/react/stream/src/Util.php @@ -0,0 +1,75 @@ + NO-OP + if (!$source->isReadable()) { + return $dest; + } + + // destination not writable => just pause() source + if (!$dest->isWritable()) { + $source->pause(); + + return $dest; + } + + $dest->emit('pipe', array($source)); + + // forward all source data events as $dest->write() + $source->on('data', $dataer = function ($data) use ($source, $dest) { + $feedMore = $dest->write($data); + + if (false === $feedMore) { + $source->pause(); + } + }); + $dest->on('close', function () use ($source, $dataer) { + $source->removeListener('data', $dataer); + $source->pause(); + }); + + // forward destination drain as $source->resume() + $dest->on('drain', $drainer = function () use ($source) { + $source->resume(); + }); + $source->on('close', function () use ($dest, $drainer) { + $dest->removeListener('drain', $drainer); + }); + + // forward end event from source as $dest->end() + $end = isset($options['end']) ? $options['end'] : true; + if ($end) { + $source->on('end', $ender = function () use ($dest) { + $dest->end(); + }); + $dest->on('close', function () use ($source, $ender) { + $source->removeListener('end', $ender); + }); + } + + return $dest; + } + + public static function forwardEvents($source, $target, array $events) + { + foreach ($events as $event) { + $source->on($event, function () use ($event, $target) { + $target->emit($event, func_get_args()); + }); + } + } +} diff --git a/assets/php/vendor/react/stream/src/WritableResourceStream.php b/assets/php/vendor/react/stream/src/WritableResourceStream.php new file mode 100644 index 0000000..7e04205 --- /dev/null +++ b/assets/php/vendor/react/stream/src/WritableResourceStream.php @@ -0,0 +1,171 @@ +stream = $stream; + $this->loop = $loop; + $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit; + $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize; + } + + public function isWritable() + { + return $this->writable; + } + + public function write($data) + { + if (!$this->writable) { + return false; + } + + $this->data .= $data; + + if (!$this->listening && $this->data !== '') { + $this->listening = true; + + $this->loop->addWriteStream($this->stream, array($this, 'handleWrite')); + } + + return !isset($this->data[$this->softLimit - 1]); + } + + public function end($data = null) + { + if (null !== $data) { + $this->write($data); + } + + $this->writable = false; + + // close immediately if buffer is already empty + // otherwise wait for buffer to flush first + if ($this->data === '') { + $this->close(); + } + } + + public function close() + { + if ($this->closed) { + return; + } + + if ($this->listening) { + $this->listening = false; + $this->loop->removeWriteStream($this->stream); + } + + $this->closed = true; + $this->writable = false; + $this->data = ''; + + $this->emit('close'); + $this->removeAllListeners(); + + if (is_resource($this->stream)) { + fclose($this->stream); + } + } + + /** @internal */ + public function handleWrite() + { + $error = null; + set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + $error = array( + 'message' => $errstr, + 'number' => $errno, + 'file' => $errfile, + 'line' => $errline + ); + }); + + if ($this->writeChunkSize === -1) { + $sent = fwrite($this->stream, $this->data); + } else { + $sent = fwrite($this->stream, $this->data, $this->writeChunkSize); + } + + restore_error_handler(); + + // Only report errors if *nothing* could be sent. + // Any hard (permanent) error will fail to send any data at all. + // Sending excessive amounts of data will only flush *some* data and then + // report a temporary error (EAGAIN) which we do not raise here in order + // to keep the stream open for further tries to write. + // Should this turn out to be a permanent error later, it will eventually + // send *nothing* and we can detect this. + if ($sent === 0 || $sent === false) { + if ($error !== null) { + $error = new \ErrorException( + $error['message'], + 0, + $error['number'], + $error['file'], + $error['line'] + ); + } + + $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error))); + $this->close(); + + return; + } + + $exceeded = isset($this->data[$this->softLimit - 1]); + $this->data = (string) substr($this->data, $sent); + + // buffer has been above limit and is now below limit + if ($exceeded && !isset($this->data[$this->softLimit - 1])) { + $this->emit('drain'); + } + + // buffer is now completely empty => stop trying to write + if ($this->data === '') { + // stop waiting for resource to be writable + if ($this->listening) { + $this->loop->removeWriteStream($this->stream); + $this->listening = false; + } + + // buffer is end()ing and now completely empty => close buffer + if (!$this->writable) { + $this->close(); + } + } + } +} diff --git a/assets/php/vendor/react/stream/src/WritableStreamInterface.php b/assets/php/vendor/react/stream/src/WritableStreamInterface.php new file mode 100644 index 0000000..3bc932e --- /dev/null +++ b/assets/php/vendor/react/stream/src/WritableStreamInterface.php @@ -0,0 +1,347 @@ +on('drain', function () use ($stream) { + * echo 'Stream is now ready to accept more data'; + * }); + * ``` + * + * This event SHOULD be emitted once every time the buffer became full + * previously and is now ready to accept more data. + * In other words, this event MAY be emitted any number of times, which may + * be zero times if the buffer never became full in the first place. + * This event SHOULD NOT be emitted if the buffer has not become full + * previously. + * + * This event is mostly used internally, see also `write()` for more details. + * + * pipe event: + * The `pipe` event will be emitted whenever a readable stream is `pipe()`d + * into this stream. + * The event receives a single `ReadableStreamInterface` argument for the + * source stream. + * + * ```php + * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { + * echo 'Now receiving piped data'; + * + * // explicitly close target if source emits an error + * $source->on('error', function () use ($stream) { + * $stream->close(); + * }); + * }); + * + * $source->pipe($stream); + * ``` + * + * This event MUST be emitted once for each readable stream that is + * successfully piped into this destination stream. + * In other words, this event MAY be emitted any number of times, which may + * be zero times if no stream is ever piped into this stream. + * This event MUST NOT be emitted if either the source is not readable + * (closed already) or this destination is not writable (closed already). + * + * This event is mostly used internally, see also `pipe()` for more details. + * + * error event: + * The `error` event will be emitted once a fatal error occurs, usually while + * trying to write to this stream. + * The event receives a single `Exception` argument for the error instance. + * + * ```php + * $stream->on('error', function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * This event SHOULD be emitted once the stream detects a fatal error, such + * as a fatal transmission error. + * It SHOULD NOT be emitted after a previous `error` or `close` event. + * It MUST NOT be emitted if this is not a fatal error condition, such as + * a temporary network issue that did not cause any data to be lost. + * + * After the stream errors, it MUST close the stream and SHOULD thus be + * followed by a `close` event and then switch to non-writable mode, see + * also `close()` and `isWritable()`. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * only deal with data transmission and may choose + * to only emit this for a fatal transmission error once and will then + * close (terminate) the stream in response. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements an `error` event. + * In other words, an error may occur while either reading or writing the + * stream which should result in the same error processing. + * + * close event: + * The `close` event will be emitted once the stream closes (terminates). + * + * ```php + * $stream->on('close', function () { + * echo 'CLOSED'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * the stream ever terminates. + * It SHOULD NOT be emitted after a previous `close` event. + * + * After the stream is closed, it MUST switch to non-writable mode, + * see also `isWritable()`. + * + * This event SHOULD be emitted whenever the stream closes, irrespective of + * whether this happens implicitly due to an unrecoverable error or + * explicitly when either side closes the stream. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will likely choose to emit this event after flushing the buffer from + * the `end()` method, after receiving a *successful* `end` event or after + * a fatal transmission `error` event. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements a `close` event. + * In other words, after receiving this event, the stream MUST switch into + * non-writable AND non-readable mode, see also `isReadable()`. + * Note that this event should not be confused with the `end` event. + * + * The event callback functions MUST be a valid `callable` that obeys strict + * parameter definitions and MUST accept event parameters exactly as documented. + * The event callback functions MUST NOT throw an `Exception`. + * The return value of the event callback functions will be ignored and has no + * effect, so for performance reasons you're recommended to not return any + * excessive data structures. + * + * Every implementation of this interface MUST follow these event semantics in + * order to be considered a well-behaving stream. + * + * > Note that higher-level implementations of this interface may choose to + * define additional events with dedicated semantics not defined as part of + * this low-level stream specification. Conformance with these event semantics + * is out of scope for this interface, so you may also have to refer to the + * documentation of such a higher-level implementation. + * + * @see EventEmitterInterface + * @see DuplexStreamInterface + */ +interface WritableStreamInterface extends EventEmitterInterface +{ + /** + * Checks whether this stream is in a writable state (not closed already). + * + * This method can be used to check if the stream still accepts writing + * any data or if it is ended or closed already. + * Writing any data to a non-writable stream is a NO-OP: + * + * ```php + * assert($stream->isWritable() === false); + * + * $stream->write('end'); // NO-OP + * $stream->end('end'); // NO-OP + * ``` + * + * A successfully opened stream always MUST start in writable mode. + * + * Once the stream ends or closes, it MUST switch to non-writable mode. + * This can happen any time, explicitly through `end()` or `close()` or + * implicitly due to a remote close or an unrecoverable transmission error. + * Once a stream has switched to non-writable mode, it MUST NOT transition + * back to writable mode. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements an `isReadable()` + * method. Unless this is a half-open duplex stream, they SHOULD usually + * have the same return value. + * + * @return bool + */ + public function isWritable(); + + /** + * Write some data into the stream. + * + * A successful write MUST be confirmed with a boolean `true`, which means + * that either the data was written (flushed) immediately or is buffered and + * scheduled for a future write. Note that this interface gives you no + * control over explicitly flushing the buffered data, as finding the + * appropriate time for this is beyond the scope of this interface and left + * up to the implementation of this interface. + * + * Many common streams (such as a TCP/IP connection or file-based stream) + * may choose to buffer all given data and schedule a future flush by using + * an underlying EventLoop to check when the resource is actually writable. + * + * If a stream cannot handle writing (or flushing) the data, it SHOULD emit + * an `error` event and MAY `close()` the stream if it can not recover from + * this error. + * + * If the internal buffer is full after adding `$data`, then `write()` + * SHOULD return `false`, indicating that the caller should stop sending + * data until the buffer drains. + * The stream SHOULD send a `drain` event once the buffer is ready to accept + * more data. + * + * Similarly, if the the stream is not writable (already in a closed state) + * it MUST NOT process the given `$data` and SHOULD return `false`, + * indicating that the caller should stop sending data. + * + * The given `$data` argument MAY be of mixed type, but it's usually + * recommended it SHOULD be a `string` value or MAY use a type that allows + * representation as a `string` for maximum compatibility. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will only accept the raw (binary) payload data that is transferred over + * the wire as chunks of `string` values. + * + * Due to the stream-based nature of this, the sender may send any number + * of chunks with varying sizes. There are no guarantees that these chunks + * will be received with the exact same framing the sender intended to send. + * In other words, many lower-level protocols (such as TCP/IP) transfer the + * data in chunks that may be anywhere between single-byte values to several + * dozens of kilobytes. You may want to apply a higher-level protocol to + * these low-level data chunks in order to achieve proper message framing. + * + * @param mixed|string $data + * @return bool + */ + public function write($data); + + /** + * Successfully ends the stream (after optionally sending some final data). + * + * This method can be used to successfully end the stream, i.e. close + * the stream after sending out all data that is currently buffered. + * + * ```php + * $stream->write('hello'); + * $stream->write('world'); + * $stream->end(); + * ``` + * + * If there's no data currently buffered and nothing to be flushed, then + * this method MAY `close()` the stream immediately. + * + * If there's still data in the buffer that needs to be flushed first, then + * this method SHOULD try to write out this data and only then `close()` + * the stream. + * Once the stream is closed, it SHOULD emit a `close` event. + * + * Note that this interface gives you no control over explicitly flushing + * the buffered data, as finding the appropriate time for this is beyond the + * scope of this interface and left up to the implementation of this + * interface. + * + * Many common streams (such as a TCP/IP connection or file-based stream) + * may choose to buffer all given data and schedule a future flush by using + * an underlying EventLoop to check when the resource is actually writable. + * + * You can optionally pass some final data that is written to the stream + * before ending the stream. If a non-`null` value is given as `$data`, then + * this method will behave just like calling `write($data)` before ending + * with no data. + * + * ```php + * // shorter version + * $stream->end('bye'); + * + * // same as longer version + * $stream->write('bye'); + * $stream->end(); + * ``` + * + * After calling this method, the stream MUST switch into a non-writable + * mode, see also `isWritable()`. + * This means that no further writes are possible, so any additional + * `write()` or `end()` calls have no effect. + * + * ```php + * $stream->end(); + * assert($stream->isWritable() === false); + * + * $stream->write('nope'); // NO-OP + * $stream->end(); // NO-OP + * ``` + * + * If this stream is a `DuplexStreamInterface`, calling this method SHOULD + * also end its readable side, unless the stream supports half-open mode. + * In other words, after calling this method, these streams SHOULD switch + * into non-writable AND non-readable mode, see also `isReadable()`. + * This implies that in this case, the stream SHOULD NOT emit any `data` + * or `end` events anymore. + * Streams MAY choose to use the `pause()` method logic for this, but + * special care may have to be taken to ensure a following call to the + * `resume()` method SHOULD NOT continue emitting readable events. + * + * Note that this method should not be confused with the `close()` method. + * + * @param mixed|string|null $data + * @return void + */ + public function end($data = null); + + /** + * Closes the stream (forcefully). + * + * This method can be used to forcefully close the stream, i.e. close + * the stream without waiting for any buffered data to be flushed. + * If there's still data in the buffer, this data SHOULD be discarded. + * + * ```php + * $stream->close(); + * ``` + * + * Once the stream is closed, it SHOULD emit a `close` event. + * Note that this event SHOULD NOT be emitted more than once, in particular + * if this method is called multiple times. + * + * After calling this method, the stream MUST switch into a non-writable + * mode, see also `isWritable()`. + * This means that no further writes are possible, so any additional + * `write()` or `end()` calls have no effect. + * + * ```php + * $stream->close(); + * assert($stream->isWritable() === false); + * + * $stream->write('nope'); // NO-OP + * $stream->end(); // NO-OP + * ``` + * + * Note that this method should not be confused with the `end()` method. + * Unlike the `end()` method, this method does not take care of any existing + * buffers and simply discards any buffer contents. + * Likewise, this method may also be called after calling `end()` on a + * stream in order to stop waiting for the stream to flush its final data. + * + * ```php + * $stream->end(); + * $loop->addTimer(1.0, function () use ($stream) { + * $stream->close(); + * }); + * ``` + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements a `close()` method. + * In other words, after calling this method, the stream MUST switch into + * non-writable AND non-readable mode, see also `isReadable()`. + * + * @return void + * @see ReadableStreamInterface::close() + */ + public function close(); +} diff --git a/assets/php/vendor/react/stream/tests/CallableStub.php b/assets/php/vendor/react/stream/tests/CallableStub.php new file mode 100644 index 0000000..31cc834 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/CallableStub.php @@ -0,0 +1,10 @@ +getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('close'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('isWritable') + ->willReturn(false); + + $composite = new CompositeStream($readable, $writable); + + $composite->on('close', $this->expectCallableNever()); + $composite->close(); + } + + /** @test */ + public function itShouldCloseWritableIfNotReadable() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(false); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('close'); + + $composite = new CompositeStream($readable, $writable); + + $composite->on('close', $this->expectCallableNever()); + $composite->close(); + } + + /** @test */ + public function itShouldForwardWritableCallsToWritableStream() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('write') + ->with('foo'); + $writable + ->expects($this->exactly(2)) + ->method('isWritable') + ->willReturn(true); + + $composite = new CompositeStream($readable, $writable); + $composite->write('foo'); + $composite->isWritable(); + } + + /** @test */ + public function itShouldForwardReadableCallsToReadableStream() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->exactly(2)) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('pause'); + $readable + ->expects($this->once()) + ->method('resume'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + + $composite = new CompositeStream($readable, $writable); + $composite->isReadable(); + $composite->pause(); + $composite->resume(); + } + + /** @test */ + public function itShouldNotForwardResumeIfStreamIsNotWritable() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->never()) + ->method('resume'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->exactly(2)) + ->method('isWritable') + ->willReturnOnConsecutiveCalls(true, false); + + $composite = new CompositeStream($readable, $writable); + $composite->resume(); + } + + /** @test */ + public function endShouldDelegateToWritableWithData() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('end') + ->with('foo'); + + $composite = new CompositeStream($readable, $writable); + $composite->end('foo'); + } + + /** @test */ + public function closeShouldCloseBothStreams() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('close'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->once()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('close'); + + $composite = new CompositeStream($readable, $writable); + $composite->close(); + } + + /** @test */ + public function itShouldForwardCloseOnlyOnce() + { + $readable = new ThroughStream(); + $writable = new ThroughStream(); + + $composite = new CompositeStream($readable, $writable); + $composite->on('close', $this->expectCallableOnce()); + + $readable->close(); + $writable->close(); + } + + /** @test */ + public function itShouldForwardCloseAndRemoveAllListeners() + { + $in = new ThroughStream(); + + $composite = new CompositeStream($in, $in); + $composite->on('close', $this->expectCallableOnce()); + + $this->assertTrue($composite->isReadable()); + $this->assertTrue($composite->isWritable()); + $this->assertCount(1, $composite->listeners('close')); + + $composite->close(); + + $this->assertFalse($composite->isReadable()); + $this->assertFalse($composite->isWritable()); + $this->assertCount(0, $composite->listeners('close')); + } + + /** @test */ + public function itShouldReceiveForwardedEvents() + { + $readable = new ThroughStream(); + $writable = new ThroughStream(); + + $composite = new CompositeStream($readable, $writable); + $composite->on('data', $this->expectCallableOnce()); + $composite->on('drain', $this->expectCallableOnce()); + + $readable->emit('data', array('foo')); + $writable->emit('drain'); + } + + /** @test */ + public function itShouldHandlePipingCorrectly() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->once()) + ->method('isReadable') + ->willReturn(true); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable->expects($this->any())->method('isWritable')->willReturn(True); + $writable + ->expects($this->once()) + ->method('write') + ->with('foo'); + + $composite = new CompositeStream($readable, $writable); + + $input = new ThroughStream(); + $input->pipe($composite); + $input->emit('data', array('foo')); + } + + /** @test */ + public function itShouldForwardPipeCallsToReadableStream() + { + $readable = new ThroughStream(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable->expects($this->any())->method('isWritable')->willReturn(True); + + $composite = new CompositeStream($readable, $writable); + + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $output->expects($this->any())->method('isWritable')->willReturn(True); + $output + ->expects($this->once()) + ->method('write') + ->with('foo'); + + $composite->pipe($output); + $readable->emit('data', array('foo')); + } +} diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php new file mode 100644 index 0000000..fb5f02a --- /dev/null +++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php @@ -0,0 +1,352 @@ +markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $bufferSize = 4096; + $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize); + $streamB = new DuplexResourceStream($sockB, $loop, $bufferSize); + + $testString = str_repeat("*", $bufferSize + 1); + + $buffer = ""; + $streamB->on('data', function ($data) use (&$buffer) { + $buffer .= $data; + }); + + $streamA->write($testString); + + $this->loopTick($loop); + $this->loopTick($loop); + $this->loopTick($loop); + + $streamA->close(); + $streamB->close(); + + $this->assertEquals($testString, $buffer); + } + + /** + * @dataProvider loopProvider + */ + public function testWriteLargeChunk($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $streamA = new DuplexResourceStream($sockA, $loop); + $streamB = new DuplexResourceStream($sockB, $loop); + + // limit seems to be 192 KiB + $size = 256 * 1024; + + // sending side sends and expects clean close with no errors + $streamA->end(str_repeat('*', $size)); + $streamA->on('close', $this->expectCallableOnce()); + $streamA->on('error', $this->expectCallableNever()); + + // receiving side counts bytes and expects clean close with no errors + $received = 0; + $streamB->on('data', function ($chunk) use (&$received) { + $received += strlen($chunk); + }); + $streamB->on('close', $this->expectCallableOnce()); + $streamB->on('error', $this->expectCallableNever()); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + + $this->assertEquals($size, $received); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $streamA = new DuplexResourceStream($sockA, $loop); + $streamB = new DuplexResourceStream($sockB, $loop); + + // end streamA without writing any data + $streamA->end(); + + // streamB should not emit any data + $streamB->on('data', $this->expectCallableNever()); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0); + + $streamA = new DuplexResourceStream($sockA, $loop); + $streamB = new DuplexResourceStream($sockB, $loop); + + // end streamA without writing any data + $streamA->pause(); + $streamA->write('hello'); + $streamA->on('close', $this->expectCallableOnce()); + + $streamB->on('data', $this->expectCallableNever()); + $streamB->close(); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $server = stream_socket_server('tcp://127.0.0.1:0'); + + $client = stream_socket_client(stream_socket_get_name($server, false)); + $peer = stream_socket_accept($server); + + $streamA = new DuplexResourceStream($client, $loop); + $streamB = new DuplexResourceStream($peer, $loop); + + // end streamA without writing any data + $streamA->pause(); + $streamA->write('hello'); + $streamA->on('close', $this->expectCallableOnce()); + + $streamB->on('data', $this->expectCallableNever()); + $streamB->close(); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $server = stream_socket_server('tcp://127.0.0.1:0'); + + $client = stream_socket_client(stream_socket_get_name($server, false)); + $peer = stream_socket_accept($server); + + $streamA = new DuplexResourceStream($peer, $loop); + $streamB = new DuplexResourceStream($client, $loop); + + // end streamA without writing any data + $streamA->pause(); + $streamA->write('hello'); + $streamA->on('close', $this->expectCallableOnce()); + + $streamB->on('data', $this->expectCallableNever()); + $streamB->close(); + + $loop->run(); + + $streamA->close(); + $streamB->close(); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop); + $stream->on('data', $this->expectCallableOnceWith("test\n")); + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + + $this->assertEquals("a\n" . "b\n" . "c\n", $buffer); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsLongChunksFromProcessPipe($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop); + + $bytes = 0; + $stream->on('data', function ($chunk) use (&$bytes) { + $bytes += strlen($chunk); + }); + + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + + $this->assertEquals(12345 * 1234, $bytes); + } + + /** + * @dataProvider loopProvider + */ + public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory) + { + if (true !== $condition()) { + return $this->markTestSkipped('Loop implementation not available'); + } + + $loop = $loopFactory(); + + $stream = new ReadableResourceStream(popen('true', 'r'), $loop); + $stream->on('data', $this->expectCallableNever()); + $stream->on('end', $this->expectCallableOnce()); + $stream->on('error', $this->expectCallableNever()); + + $loop->run(); + } + + private function loopTick(LoopInterface $loop) + { + $loop->addTimer(0, function () use ($loop) { + $loop->stop(); + }); + $loop->run(); + } +} diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php new file mode 100644 index 0000000..3212ae8 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php @@ -0,0 +1,495 @@ +createLoopMock(); + + new DuplexResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = @fopen($name, 'r+eANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + $buffer = new DuplexResourceStream($stream, $loop); + $buffer->close(); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnInvalidStream() + { + $loop = $this->createLoopMock(); + + new DuplexResourceStream('breakme', $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStream() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM does not report fopen mode for STDOUT'); + } + + $loop = $this->createLoopMock(); + + new DuplexResourceStream(STDOUT, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'weANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + new DuplexResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @expectedException RunTimeException + */ + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + { + if (!in_array('blocking', stream_get_wrappers())) { + stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); + } + + $stream = fopen('blocking://test', 'r+'); + $loop = $this->createLoopMock(); + + new DuplexResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorAcceptsBuffer() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $conn = new DuplexResourceStream($stream, $loop, null, $buffer); + } + + public function testCloseShouldEmitCloseEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('close', $this->expectCallableOnce()); + $conn->on('end', $this->expectCallableNever()); + + $conn->close(); + + $this->assertFalse($conn->isReadable()); + } + + public function testEndShouldEndBuffer() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $buffer->expects($this->once())->method('end')->with('foo'); + + $conn = new DuplexResourceStream($stream, $loop, null, $buffer); + $conn->end('foo'); + } + + + public function testEndAfterCloseIsNoOp() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $buffer->expects($this->never())->method('end'); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->close(); + $conn->end(); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobar\n", $capturedData); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkMatchingBufferSize() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop, 4321); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(4321, strlen($capturedData)); + } + + /** + * @covers React\Stream\DuplexResourceStream::__construct + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop, -1); + + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(100000, strlen($capturedData)); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testEmptyStreamShouldNotEmitData() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + + $conn->handleData($stream); + } + + /** + * @covers React\Stream\DuplexResourceStream::write + */ + public function testWrite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->write("foo\n"); + + rewind($stream); + $this->assertSame("foo\n", fgets($stream)); + } + + /** + * @covers React\Stream\DuplexResourceStream::end + * @covers React\Stream\DuplexResourceStream::isReadable + * @covers React\Stream\DuplexResourceStream::isWritable + */ + public function testEnd() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->end(); + + $this->assertFalse(is_resource($stream)); + $this->assertFalse($conn->isReadable()); + $this->assertFalse($conn->isWritable()); + } + + /** + * @covers React\Stream\DuplexResourceStream::end + */ + public function testEndRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->end('bye'); + } + + /** + * @covers React\Stream\DuplexResourceStream::pause + */ + public function testPauseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->pause(); + $conn->pause(); + } + + /** + * @covers React\Stream\DuplexResourceStream::pause + */ + public function testResumeDoesAddStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->resume(); + $conn->resume(); + } + + /** + * @covers React\Stream\DuplexResourceStream::close + */ + public function testCloseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->close(); + } + + /** + * @covers React\Stream\DuplexResourceStream::close + */ + public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->pause(); + $conn->close(); + } + + /** + * @covers React\Stream\DuplexResourceStream::close + */ + public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->close(); + $conn->resume(); + } + + public function testEndedStreamsShouldNotWrite() + { + $file = tempnam(sys_get_temp_dir(), 'reactphptest_'); + $stream = fopen($file, 'r+'); + $loop = $this->createWriteableLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->write("foo\n"); + $conn->end(); + + $res = $conn->write("bar\n"); + $stream = fopen($file, 'r'); + + $this->assertSame("foo\n", fgets($stream)); + $this->assertFalse($res); + + unlink($file); + } + + public function testPipeShouldReturnDestination() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $this->assertSame($dest, $conn->pipe($dest)); + } + + public function testBufferEventsShouldBubbleUp() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $conn = new DuplexResourceStream($stream, $loop, null, $buffer); + + $conn->on('drain', $this->expectCallableOnce()); + $conn->on('error', $this->expectCallableOnce()); + + $buffer->emit('drain'); + $buffer->emit('error', array(new \RuntimeException('Whoops'))); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testClosingStreamInDataEventShouldNotTriggerError() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('error', $this->expectCallableNever()); + $conn->on('data', function ($data) use ($conn) { + $conn->close(); + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataFiltered() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which removes every 'a' when reading + Filter\append($stream, function ($chunk) { + return str_replace('a', '', $chunk); + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobr\n", $capturedData); + } + + /** + * @covers React\Stream\DuplexResourceStream::handleData + */ + public function testDataErrorShouldEmitErrorAndClose() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which returns an error when encountering an 'a' when reading + Filter\append($stream, function ($chunk) { + if (strpos($chunk, 'a') !== false) { + throw new \Exception('Invalid'); + } + return $chunk; + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $conn = new DuplexResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + $conn->on('error', $this->expectCallableOnce()); + $conn->on('close', $this->expectCallableOnce()); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + private function createWriteableLoopMock() + { + $loop = $this->createLoopMock(); + $loop + ->expects($this->once()) + ->method('addWriteStream') + ->will($this->returnCallback(function ($stream, $listener) { + call_user_func($listener, $stream); + })); + + return $loop; + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php new file mode 100644 index 0000000..39c0487 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php @@ -0,0 +1,35 @@ +on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + public function testUploadBiggerBlockPlain() + { + $size = 50 * 1000; + $stream = stream_socket_client('tcp://httpbin.org:80'); + + $loop = Factory::create(); + $stream = new DuplexResourceStream($stream, $loop); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + public function testUploadKilobyteSecure() + { + $size = 1000; + $stream = stream_socket_client('tls://httpbin.org:443'); + + $loop = Factory::create(); + $stream = new DuplexResourceStream($stream, $loop); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + public function testUploadBiggerBlockSecureRequiresSmallerChunkSize() + { + $size = 50 * 1000; + $stream = stream_socket_client('tls://httpbin.org:443'); + + $loop = Factory::create(); + $stream = new DuplexResourceStream( + $stream, + $loop, + null, + new WritableResourceStream($stream, $loop, null, 8192) + ); + + $buffer = ''; + $stream->on('data', function ($chunk) use (&$buffer) { + $buffer .= $chunk; + }); + + $stream->on('error', $this->expectCallableNever()); + + $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size)); + + $this->awaitStreamClose($stream, $loop); + + $this->assertNotEquals('', $buffer); + } + + private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0) + { + $stream->on('close', function () use ($loop) { + $loop->stop(); + }); + + $that = $this; + $loop->addTimer($timeout, function () use ($loop, $that) { + $loop->stop(); + $that->fail('Timed out while waiting for stream to close'); + }); + + $loop->run(); + } +} diff --git a/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php new file mode 100644 index 0000000..20da96f --- /dev/null +++ b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php @@ -0,0 +1,372 @@ +createLoopMock(); + + new ReadableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = @fopen($name, 'r+eANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + $buffer = new ReadableResourceStream($stream, $loop); + $buffer->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnInvalidStream() + { + $loop = $this->createLoopMock(); + + new ReadableResourceStream(false, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStream() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM does not report fopen mode for STDOUT'); + } + + $loop = $this->createLoopMock(); + + new ReadableResourceStream(STDOUT, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'weANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + new ReadableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @expectedException RuntimeException + */ + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + { + if (!in_array('blocking', stream_get_wrappers())) { + stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); + } + + $stream = fopen('blocking://test', 'r+'); + $loop = $this->createLoopMock(); + + new ReadableResourceStream($stream, $loop); + } + + + public function testCloseShouldEmitCloseEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('close', $this->expectCallableOnce()); + + $conn->close(); + + $this->assertFalse($conn->isReadable()); + } + + public function testCloseTwiceShouldEmitCloseEventOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('close', $this->expectCallableOnce()); + + $conn->close(); + $conn->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataEvent() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobar\n", $capturedData); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkMatchingBufferSize() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop, 4321); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(4321, strlen($capturedData)); + } + + /** + * @covers React\Stream\ReadableResourceStream::__construct + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop, -1); + + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, str_repeat("a", 100000)); + rewind($stream); + + $conn->handleData($stream); + + $this->assertTrue($conn->isReadable()); + $this->assertEquals(100000, strlen($capturedData)); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testEmptyStreamShouldNotEmitData() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + + $conn->handleData($stream); + } + + public function testPipeShouldReturnDestination() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $this->assertSame($dest, $conn->pipe($dest)); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testClosingStreamInDataEventShouldNotTriggerError() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('error', $this->expectCallableNever()); + $conn->on('data', function ($data) use ($conn) { + $conn->close(); + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + /** + * @covers React\Stream\ReadableResourceStream::pause + */ + public function testPauseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->pause(); + $conn->pause(); + } + + /** + * @covers React\Stream\ReadableResourceStream::pause + */ + public function testResumeDoesAddStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->resume(); + $conn->resume(); + } + + /** + * @covers React\Stream\ReadableResourceStream::close + */ + public function testCloseRemovesReadStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::close + */ + public function testCloseAfterPauseRemovesReadStreamFromLoopOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + $loop->expects($this->once())->method('removeReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->pause(); + $conn->close(); + } + + /** + * @covers React\Stream\ReadableResourceStream::close + */ + public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addReadStream')->with($stream); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->close(); + $conn->resume(); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataFiltered() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which removes every 'a' when reading + Filter\append($stream, function ($chunk) { + return str_replace('a', '', $chunk); + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $capturedData = null; + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', function ($data) use (&$capturedData) { + $capturedData = $data; + }); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + $this->assertSame("foobr\n", $capturedData); + } + + /** + * @covers React\Stream\ReadableResourceStream::handleData + */ + public function testDataErrorShouldEmitErrorAndClose() + { + $stream = fopen('php://temp', 'r+'); + + // add a filter which returns an error when encountering an 'a' when reading + Filter\append($stream, function ($chunk) { + if (strpos($chunk, 'a') !== false) { + throw new \Exception('Invalid'); + } + return $chunk; + }, STREAM_FILTER_READ); + + $loop = $this->createLoopMock(); + + $conn = new ReadableResourceStream($stream, $loop); + $conn->on('data', $this->expectCallableNever()); + $conn->on('error', $this->expectCallableOnce()); + $conn->on('close', $this->expectCallableOnce()); + + fwrite($stream, "foobar\n"); + rewind($stream); + + $conn->handleData($stream); + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } +} diff --git a/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php new file mode 100644 index 0000000..6984f24 --- /dev/null +++ b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php @@ -0,0 +1,61 @@ +emit('data', array($data)); + } + + // trigger error event + public function error($error) + { + $this->emit('error', array($error)); + } + + // trigger end event + public function end() + { + $this->emit('end', array()); + } + + public function pause() + { + $this->paused = true; + } + + public function resume() + { + $this->paused = false; + } + + public function close() + { + $this->readable = false; + + $this->emit('close'); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + Util::pipe($this, $dest, $options); + + return $dest; + } +} diff --git a/assets/php/vendor/react/stream/tests/TestCase.php b/assets/php/vendor/react/stream/tests/TestCase.php new file mode 100644 index 0000000..c8fc1db --- /dev/null +++ b/assets/php/vendor/react/stream/tests/TestCase.php @@ -0,0 +1,54 @@ +createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnceWith($value) + { + $callback = $this->createCallableMock(); + $callback + ->expects($this->once()) + ->method('__invoke') + ->with($value); + + return $callback; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock(); + } +} diff --git a/assets/php/vendor/react/stream/tests/ThroughStreamTest.php b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php new file mode 100644 index 0000000..a98badf --- /dev/null +++ b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php @@ -0,0 +1,267 @@ +write('foo'); + + $this->assertTrue($ret); + } + + /** @test */ + public function itShouldEmitAnyDataWrittenToIt() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnceWith('foo')); + $through->write('foo'); + } + + /** @test */ + public function itShouldEmitAnyDataWrittenToItPassedThruFunction() + { + $through = new ThroughStream('strtoupper'); + $through->on('data', $this->expectCallableOnceWith('FOO')); + $through->write('foo'); + } + + /** @test */ + public function itShouldEmitAnyDataWrittenToItPassedThruCallback() + { + $through = new ThroughStream('strtoupper'); + $through->on('data', $this->expectCallableOnceWith('FOO')); + $through->write('foo'); + } + + /** @test */ + public function itShouldEmitErrorAndCloseIfCallbackThrowsException() + { + $through = new ThroughStream(function () { + throw new \RuntimeException(); + }); + $through->on('error', $this->expectCallableOnce()); + $through->on('close', $this->expectCallableOnce()); + $through->on('data', $this->expectCallableNever()); + $through->on('end', $this->expectCallableNever()); + + $through->write('foo'); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd() + { + $through = new ThroughStream(function () { + throw new \RuntimeException(); + }); + $through->on('error', $this->expectCallableOnce()); + $through->on('close', $this->expectCallableOnce()); + $through->on('data', $this->expectCallableNever()); + $through->on('end', $this->expectCallableNever()); + + $through->end('foo'); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused() + { + $through = new ThroughStream(); + $through->pause(); + $ret = $through->write('foo'); + + $this->assertFalse($ret); + } + + /** @test */ + public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused() + { + $through = new ThroughStream(); + $through->pause(); + $through->write('foo'); + + $through->on('drain', $this->expectCallableOnce()); + $through->resume(); + } + + /** @test */ + public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause() + { + $through = new ThroughStream(); + $through->on('drain', $this->expectCallableNever()); + $through->pause(); + $through->resume(); + $ret = $through->write('foo'); + + $this->assertTrue($ret); + } + + /** @test */ + public function pipingStuffIntoItShouldWork() + { + $readable = new ThroughStream(); + + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnceWith('foo')); + + $readable->pipe($through); + $readable->emit('data', array('foo')); + } + + /** @test */ + public function endShouldEmitEndAndClose() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableNever()); + $through->on('end', $this->expectCallableOnce()); + $through->on('close', $this->expectCallableOnce()); + $through->end(); + } + + /** @test */ + public function endShouldCloseTheStream() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableNever()); + $through->end(); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function endShouldWriteDataBeforeClosing() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnceWith('foo')); + $through->end('foo'); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function endTwiceShouldOnlyEmitOnce() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableOnce('first')); + $through->end('first'); + $through->end('ignored'); + } + + /** @test */ + public function writeAfterEndShouldReturnFalse() + { + $through = new ThroughStream(); + $through->on('data', $this->expectCallableNever()); + $through->end(); + + $this->assertFalse($through->write('foo')); + } + + /** @test */ + public function writeDataWillCloseStreamShouldReturnFalse() + { + $through = new ThroughStream(); + $through->on('data', array($through, 'close')); + + $this->assertFalse($through->write('foo')); + } + + /** @test */ + public function writeDataToPausedShouldReturnFalse() + { + $through = new ThroughStream(); + $through->pause(); + + $this->assertFalse($through->write('foo')); + } + + /** @test */ + public function writeDataToResumedShouldReturnTrue() + { + $through = new ThroughStream(); + $through->pause(); + $through->resume(); + + $this->assertTrue($through->write('foo')); + } + + /** @test */ + public function itShouldBeReadableByDefault() + { + $through = new ThroughStream(); + $this->assertTrue($through->isReadable()); + } + + /** @test */ + public function itShouldBeWritableByDefault() + { + $through = new ThroughStream(); + $this->assertTrue($through->isWritable()); + } + + /** @test */ + public function closeShouldCloseOnce() + { + $through = new ThroughStream(); + + $through->on('close', $this->expectCallableOnce()); + + $through->close(); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function doubleCloseShouldCloseOnce() + { + $through = new ThroughStream(); + + $through->on('close', $this->expectCallableOnce()); + + $through->close(); + $through->close(); + + $this->assertFalse($through->isReadable()); + $this->assertFalse($through->isWritable()); + } + + /** @test */ + public function pipeShouldPipeCorrectly() + { + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $output->expects($this->any())->method('isWritable')->willReturn(True); + $output + ->expects($this->once()) + ->method('write') + ->with('foo'); + + $through = new ThroughStream(); + $through->pipe($output); + $through->write('foo'); + } +} diff --git a/assets/php/vendor/react/stream/tests/UtilTest.php b/assets/php/vendor/react/stream/tests/UtilTest.php new file mode 100644 index 0000000..3d113ab --- /dev/null +++ b/assets/php/vendor/react/stream/tests/UtilTest.php @@ -0,0 +1,273 @@ +getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $ret = Util::pipe($readable, $writable); + + $this->assertSame($writable, $ret); + } + + public function testPipeNonReadableSourceShouldDoNothing() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->any()) + ->method('isReadable') + ->willReturn(false); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->never()) + ->method('isWritable'); + $writable + ->expects($this->never()) + ->method('end'); + + Util::pipe($readable, $writable); + } + + public function testPipeIntoNonWritableDestinationShouldPauseSource() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->any()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('pause'); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(false); + $writable + ->expects($this->never()) + ->method('end'); + + Util::pipe($readable, $writable); + } + + public function testPipeClosingDestPausesSource() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable + ->expects($this->any()) + ->method('isReadable') + ->willReturn(true); + $readable + ->expects($this->once()) + ->method('pause'); + + $writable = new ThroughStream(); + + Util::pipe($readable, $writable); + + $writable->close(); + } + + public function testPipeWithEnd() + { + $readable = new Stub\ReadableStreamStub(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('end'); + + Util::pipe($readable, $writable); + + $readable->end(); + } + + public function testPipeWithoutEnd() + { + $readable = new Stub\ReadableStreamStub(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->never()) + ->method('end'); + + Util::pipe($readable, $writable, array('end' => false)); + + $readable->end(); + } + + public function testPipeWithTooSlowWritableShouldPauseReadable() + { + $readable = new Stub\ReadableStreamStub(); + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->once()) + ->method('write') + ->with('some data') + ->will($this->returnValue(false)); + + $readable->pipe($writable); + + $this->assertFalse($readable->paused); + $readable->write('some data'); + $this->assertTrue($readable->paused); + } + + public function testPipeWithTooSlowWritableShouldResumeOnDrain() + { + $readable = new Stub\ReadableStreamStub(); + + $onDrain = null; + + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable + ->expects($this->any()) + ->method('isWritable') + ->willReturn(true); + $writable + ->expects($this->any()) + ->method('on') + ->will($this->returnCallback(function ($name, $callback) use (&$onDrain) { + if ($name === 'drain') { + $onDrain = $callback; + } + })); + + $readable->pipe($writable); + $readable->pause(); + + $this->assertTrue($readable->paused); + $this->assertNotNull($onDrain); + $onDrain(); + $this->assertFalse($readable->paused); + } + + public function testPipeWithWritableResourceStream() + { + $readable = new Stub\ReadableStreamStub(); + + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + + $readable->pipe($buffer); + + $readable->write('hello, I am some '); + $readable->write('random data'); + + $buffer->handleWrite(); + rewind($stream); + $this->assertSame('hello, I am some random data', stream_get_contents($stream)); + } + + public function testPipeSetsUpListeners() + { + $source = new ThroughStream(); + $dest = new ThroughStream(); + + $this->assertCount(0, $source->listeners('data')); + $this->assertCount(0, $source->listeners('end')); + $this->assertCount(0, $dest->listeners('drain')); + + Util::pipe($source, $dest); + + $this->assertCount(1, $source->listeners('data')); + $this->assertCount(1, $source->listeners('end')); + $this->assertCount(1, $dest->listeners('drain')); + } + + public function testPipeClosingSourceRemovesListeners() + { + $source = new ThroughStream(); + $dest = new ThroughStream(); + + Util::pipe($source, $dest); + + $source->close(); + + $this->assertCount(0, $source->listeners('data')); + $this->assertCount(0, $source->listeners('end')); + $this->assertCount(0, $dest->listeners('drain')); + } + + public function testPipeClosingDestRemovesListeners() + { + $source = new ThroughStream(); + $dest = new ThroughStream(); + + Util::pipe($source, $dest); + + $dest->close(); + + $this->assertCount(0, $source->listeners('data')); + $this->assertCount(0, $source->listeners('end')); + $this->assertCount(0, $dest->listeners('drain')); + } + + public function testPipeDuplexIntoSelfEndsOnEnd() + { + $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $readable->expects($this->any())->method('isReadable')->willReturn(true); + $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $writable->expects($this->any())->method('isWritable')->willReturn(true); + $duplex = new CompositeStream($readable, $writable); + + Util::pipe($duplex, $duplex); + + $writable->expects($this->once())->method('end'); + + $duplex->emit('end'); + } + + /** @test */ + public function forwardEventsShouldSetupForwards() + { + $source = new ThroughStream(); + $target = new ThroughStream(); + + Util::forwardEvents($source, $target, array('data')); + $target->on('data', $this->expectCallableOnce()); + $target->on('foo', $this->expectCallableNever()); + + $source->emit('data', array('hello')); + $source->emit('foo', array('bar')); + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } + + private function notEqualTo($value) + { + return new \PHPUnit_Framework_Constraint_Not($value); + } +} diff --git a/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php new file mode 100644 index 0000000..05bce9c --- /dev/null +++ b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php @@ -0,0 +1,534 @@ +createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @doesNotPerformAssertions + */ + public function testConstructorWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = @fopen($name, 'w+eANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsIfNotAValidStreamResource() + { + $stream = null; + $loop = $this->createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnReadOnlyStream() + { + $stream = fopen('php://temp', 'r'); + $loop = $this->createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode() + { + // excessive flags are ignored for temp streams, so we have to use a file stream + $name = tempnam(sys_get_temp_dir(), 'test'); + $stream = fopen($name, 'reANYTHING'); + unlink($name); + + $loop = $this->createLoopMock(); + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::__construct + * @expectedException RuntimeException + */ + public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking() + { + if (!in_array('blocking', stream_get_wrappers())) { + stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper'); + } + + $stream = fopen('blocking://test', 'r+'); + $loop = $this->createLoopMock(); + + new WritableResourceStream($stream, $loop); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWrite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + + $buffer->write("foobar\n"); + rewind($stream); + $this->assertSame("foobar\n", fread($stream, 1024)); + } + + /** + * @covers React\Stream\WritableResourceStream::write + */ + public function testWriteWithDataDoesAddResourceToLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream)); + + $buffer = new WritableResourceStream($stream, $loop); + + $buffer->write("foobar\n"); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testEmptyWriteDoesNotAddToLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->never())->method('addWriteStream'); + + $buffer = new WritableResourceStream($stream, $loop); + + $buffer->write(""); + $buffer->write(null); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWriteReturnsFalseWhenWritableResourceStreamIsFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + $loop->preventWrites = true; + + $buffer = new WritableResourceStream($stream, $loop, 4); + $buffer->on('error', $this->expectCallableNever()); + + $this->assertTrue($buffer->write("foo")); + $loop->preventWrites = false; + $this->assertFalse($buffer->write("bar\n")); + } + + /** + * @covers React\Stream\WritableResourceStream::write + */ + public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 3); + + $this->assertFalse($buffer->write("foo")); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWriteDetectsWhenOtherSideIsClosed() + { + list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + + $loop = $this->createWriteableLoopMock(); + + $buffer = new WritableResourceStream($a, $loop, 4); + $buffer->on('error', $this->expectCallableOnce()); + + fclose($b); + + $buffer->write("foo"); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testEmitsDrainAfterWriteWhichExceedsBuffer() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 2); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('drain', $this->expectCallableOnce()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testWriteInDrain() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 2); + $buffer->on('error', $this->expectCallableNever()); + + $buffer->once('drain', function () use ($buffer) { + $buffer->write("bar\n"); + $buffer->handleWrite(); + }); + + $this->assertFalse($buffer->write("foo\n")); + $buffer->handleWrite(); + + fseek($stream, 0); + $this->assertSame("foo\nbar\n", stream_get_contents($stream)); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testDrainAfterWrite() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop, 2); + + $buffer->on('drain', $this->expectCallableOnce()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('removeWriteStream')->with($stream); + + $buffer = new WritableResourceStream($stream, $loop, 2); + + $buffer->on('drain', $this->expectCallableOnce()); + + $buffer->on('close', $this->expectCallableNever()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $loop->expects($this->once())->method('removeWriteStream')->with($stream); + + $buffer = new WritableResourceStream($stream, $loop, 2); + + $buffer->on('drain', function () use ($buffer) { + $buffer->close(); + }); + + $buffer->on('close', $this->expectCallableOnce()); + + $buffer->write("foo"); + $buffer->handleWrite(); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableOnce()); + + $this->assertTrue($buffer->isWritable()); + $buffer->end(); + $this->assertFalse($buffer->isWritable()); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableNever()); + + $buffer->write('foo'); + + $this->assertTrue($buffer->isWritable()); + $buffer->end(); + $this->assertFalse($buffer->isWritable()); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes() + { + $stream = fopen('php://temp', 'r+'); + $filterBuffer = ''; + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableOnce()); + + Filter\append($stream, function ($chunk) use (&$filterBuffer) { + $filterBuffer .= $chunk; + return $chunk; + }); + + $this->assertTrue($buffer->isWritable()); + $buffer->end('final words'); + $this->assertFalse($buffer->isWritable()); + + $buffer->handleWrite(); + $this->assertSame('final words', $filterBuffer); + } + + /** + * @covers React\Stream\WritableResourceStream::end + */ + public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableNever()); + + $buffer->write('foo'); + + $this->assertTrue($buffer->isWritable()); + $buffer->end('final words'); + $this->assertFalse($buffer->isWritable()); + + rewind($stream); + $this->assertSame('', stream_get_contents($stream)); + } + + /** + * @covers React\Stream\WritableResourceStream::isWritable + * @covers React\Stream\WritableResourceStream::close + */ + public function testClose() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', $this->expectCallableNever()); + $buffer->on('close', $this->expectCallableOnce()); + + $this->assertTrue($buffer->isWritable()); + $buffer->close(); + $this->assertFalse($buffer->isWritable()); + + $this->assertEquals(array(), $buffer->listeners('close')); + } + + /** + * @covers React\Stream\WritableResourceStream::close + */ + public function testClosingAfterWriteRemovesStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + + $loop->expects($this->once())->method('removeWriteStream')->with($stream); + + $buffer->write('foo'); + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::close + */ + public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + $buffer = new WritableResourceStream($stream, $loop); + + $loop->expects($this->never())->method('removeWriteStream'); + + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::close + */ + public function testDoubleCloseWillEmitOnlyOnce() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('close', $this->expectCallableOnce()); + + $buffer->close(); + $buffer->close(); + } + + /** + * @covers React\Stream\WritableResourceStream::write + * @covers React\Stream\WritableResourceStream::close + */ + public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream() + { + $stream = fopen('php://temp', 'r+'); + $filterBuffer = ''; + $loop = $this->createLoopMock(); + + $buffer = new WritableResourceStream($stream, $loop); + + Filter\append($stream, function ($chunk) use (&$filterBuffer) { + $filterBuffer .= $chunk; + return $chunk; + }); + + $buffer->close(); + + $buffer->write('foo'); + + $buffer->handleWrite(); + $this->assertSame('', $filterBuffer); + } + + /** + * @covers React\Stream\WritableResourceStream::handleWrite + */ + public function testErrorWhenStreamResourceIsInvalid() + { + $stream = fopen('php://temp', 'r+'); + $loop = $this->createWriteableLoopMock(); + + $error = null; + + $buffer = new WritableResourceStream($stream, $loop); + $buffer->on('error', function ($message) use (&$error) { + $error = $message; + }); + + // invalidate stream resource + fclose($stream); + + $buffer->write('Attempting to write to bad stream'); + + $this->assertInstanceOf('Exception', $error); + + // the error messages differ between PHP versions, let's just check substrings + $this->assertContains('Unable to write to stream: ', $error->getMessage()); + $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true); + } + + public function testWritingToClosedStream() + { + if ('Darwin' === PHP_OS) { + $this->markTestSkipped('OS X issue with shutting down pair for writing'); + } + + list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + $loop = $this->createLoopMock(); + + $error = null; + + $buffer = new WritableResourceStream($a, $loop); + $buffer->on('error', function($message) use (&$error) { + $error = $message; + }); + + $buffer->write('foo'); + $buffer->handleWrite(); + stream_socket_shutdown($b, STREAM_SHUT_RD); + stream_socket_shutdown($a, STREAM_SHUT_RD); + $buffer->write('bar'); + $buffer->handleWrite(); + + $this->assertInstanceOf('Exception', $error); + $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage()); + } + + private function createWriteableLoopMock() + { + $loop = $this->createLoopMock(); + $loop->preventWrites = false; + $loop + ->expects($this->any()) + ->method('addWriteStream') + ->will($this->returnCallback(function ($stream, $listener) use ($loop) { + if (!$loop->preventWrites) { + call_user_func($listener, $stream); + } + })); + + return $loop; + } + + private function createLoopMock() + { + return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + } +} diff --git a/assets/php/vendor/symfony/http-foundation/.gitignore b/assets/php/vendor/symfony/http-foundation/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/assets/php/vendor/symfony/http-foundation/AcceptHeader.php b/assets/php/vendor/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 0000000..d174026 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,168 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/AcceptHeaderItem.php b/assets/php/vendor/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 0000000..c69dbbb --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,209 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/ApacheRequest.php b/assets/php/vendor/symfony/http-foundation/ApacheRequest.php new file mode 100644 index 0000000..84803eb --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/ApacheRequest.php @@ -0,0 +1,43 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/BinaryFileResponse.php b/assets/php/vendor/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 0000000..1010223 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,359 @@ + + * + * 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 + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +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/assets/php/vendor/symfony/http-foundation/CHANGELOG.md b/assets/php/vendor/symfony/http-foundation/CHANGELOG.md new file mode 100644 index 0000000..ee5b6ce --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/http-foundation/Cookie.php b/assets/php/vendor/symfony/http-foundation/Cookie.php new file mode 100644 index 0000000..4519a6a --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Cookie.php @@ -0,0 +1,289 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/assets/php/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 0000000..5fcf5b4 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,21 @@ + + * + * 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 + */ +class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/assets/php/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/assets/php/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php new file mode 100644 index 0000000..478d0dc --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/assets/php/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php new file mode 100644 index 0000000..ae7a5f1 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -0,0 +1,20 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/assets/php/vendor/symfony/http-foundation/ExpressionRequestMatcher.php new file mode 100644 index 0000000..e9c8441 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/ExpressionRequestMatcher.php @@ -0,0 +1,47 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 0000000..3b8e41d --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,28 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/Exception/FileException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 0000000..fad5133 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * 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 + */ +class FileException extends \RuntimeException +{ +} diff --git a/assets/php/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 0000000..bfcc37e --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,28 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..0444b87 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/File/Exception/UploadException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 0000000..7074e76 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * 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 + */ +class UploadException extends FileException +{ +} diff --git a/assets/php/vendor/symfony/http-foundation/File/File.php b/assets/php/vendor/symfony/http-foundation/File/File.php new file mode 100644 index 0000000..e2a6768 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/File.php @@ -0,0 +1,136 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php new file mode 100644 index 0000000..263fb32 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php @@ -0,0 +1,94 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php new file mode 100644 index 0000000..d19a0e5 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php @@ -0,0 +1,27 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php new file mode 100644 index 0000000..c2ac676 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,85 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php new file mode 100644 index 0000000..9b42835 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php @@ -0,0 +1,69 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php new file mode 100644 index 0000000..77bf51b --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -0,0 +1,808 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php new file mode 100644 index 0000000..e3ef45e --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php @@ -0,0 +1,142 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php new file mode 100644 index 0000000..f8c3ad2 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php @@ -0,0 +1,35 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/File/Stream.php b/assets/php/vendor/symfony/http-foundation/File/Stream.php new file mode 100644 index 0000000..69ae74c --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/Stream.php @@ -0,0 +1,28 @@ + + * + * 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 + */ +class Stream extends File +{ + /** + * {@inheritdoc} + */ + public function getSize() + { + return false; + } +} diff --git a/assets/php/vendor/symfony/http-foundation/File/UploadedFile.php b/assets/php/vendor/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 0000000..082d8d5 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,266 @@ + + * + * 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 + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +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/assets/php/vendor/symfony/http-foundation/FileBag.php b/assets/php/vendor/symfony/http-foundation/FileBag.php new file mode 100644 index 0000000..5edd0e6 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/FileBag.php @@ -0,0 +1,144 @@ + + * + * 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 + * @author Bulat Shakirzyanov + */ +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/assets/php/vendor/symfony/http-foundation/HeaderBag.php b/assets/php/vendor/symfony/http-foundation/HeaderBag.php new file mode 100644 index 0000000..7aaa52a --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,331 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/IpUtils.php b/assets/php/vendor/symfony/http-foundation/IpUtils.php new file mode 100644 index 0000000..86d135b --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/IpUtils.php @@ -0,0 +1,156 @@ + + * + * 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 + */ +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 + * + * @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/assets/php/vendor/symfony/http-foundation/JsonResponse.php b/assets/php/vendor/symfony/http-foundation/JsonResponse.php new file mode 100644 index 0000000..137ac33 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,220 @@ + + * + * 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 + */ +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 + $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/assets/php/vendor/symfony/http-foundation/LICENSE b/assets/php/vendor/symfony/http-foundation/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/http-foundation/ParameterBag.php b/assets/php/vendor/symfony/http-foundation/ParameterBag.php new file mode 100644 index 0000000..257ef8b --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,234 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/README.md b/assets/php/vendor/symfony/http-foundation/README.md new file mode 100644 index 0000000..8907f0b --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/http-foundation/RedirectResponse.php b/assets/php/vendor/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 0000000..01681dc --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/RedirectResponse.php @@ -0,0 +1,109 @@ + + * + * 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 + */ +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(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/assets/php/vendor/symfony/http-foundation/Request.php b/assets/php/vendor/symfony/http-foundation/Request.php new file mode 100644 index 0000000..164fb4e --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,2154 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/RequestMatcher.php b/assets/php/vendor/symfony/http-foundation/RequestMatcher.php new file mode 100644 index 0000000..076d077 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/RequestMatcher.php @@ -0,0 +1,178 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/RequestMatcherInterface.php b/assets/php/vendor/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 0000000..c26db3e --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,27 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/RequestStack.php b/assets/php/vendor/symfony/http-foundation/RequestStack.php new file mode 100644 index 0000000..3d9cfd0 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/RequestStack.php @@ -0,0 +1,103 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Response.php b/assets/php/vendor/symfony/http-foundation/Response.php new file mode 100644 index 0000000..6f8a623 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1298 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/ResponseHeaderBag.php b/assets/php/vendor/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 0000000..11a8593 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,340 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/ServerBag.php b/assets/php/vendor/symfony/http-foundation/ServerBag.php new file mode 100644 index 0000000..19d2022 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/ServerBag.php @@ -0,0 +1,102 @@ + + * + * 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 + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +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/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 0000000..ea1fda2 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,148 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 0000000..0d8d179 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,72 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/assets/php/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 0000000..abbf37e --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,153 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/assets/php/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 0000000..77521c2 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,161 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 0000000..12fb740 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,152 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 0000000..80e97f1 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,93 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Session.php b/assets/php/vendor/symfony/http-foundation/Session/Session.php new file mode 100644 index 0000000..a46cffb --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,273 @@ + + * + * 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 + * @author Drak + */ +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/assets/php/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/assets/php/vendor/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 0000000..8e37d06 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,46 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/assets/php/vendor/symfony/http-foundation/Session/SessionBagProxy.php new file mode 100644 index 0000000..307836d --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/SessionBagProxy.php @@ -0,0 +1,82 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Session/SessionInterface.php b/assets/php/vendor/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 0000000..95fca85 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,180 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php new file mode 100644 index 0000000..6ae1355 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -0,0 +1,168 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php new file mode 100644 index 0000000..90726be --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php @@ -0,0 +1,120 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 0000000..dd37eae --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,124 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 0000000..7d3fa21 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,255 @@ + + * + * 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 + * + * @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..ensureIndex( + * { "": 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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 0000000..4e9704b --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,55 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php new file mode 100644 index 0000000..9be4528 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php @@ -0,0 +1,24 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 0000000..8d19315 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,76 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 0000000..c8737be --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,910 @@ + + * + * 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 + * @author Michael Williams + * @author Tobias Schultze + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php new file mode 100644 index 0000000..2281192 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -0,0 +1,103 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php new file mode 100644 index 0000000..1541ec4 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php @@ -0,0 +1,92 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 0000000..6f59af4 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -0,0 +1,168 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 0000000..027f4ef --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,256 @@ + + * + * 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 + * @author Bulat Shakirzyanov + * @author Drak + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 0000000..14f4270 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,152 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 0000000..0dfad9a --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,445 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 0000000..662ed50 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,59 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 0000000..09c9248 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,122 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php new file mode 100644 index 0000000..082eed1 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php @@ -0,0 +1,40 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 0000000..53c1209 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,85 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000..66e8b33 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,137 @@ + + * + * 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 + * @author Drak + */ +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/assets/php/vendor/symfony/http-foundation/StreamedResponse.php b/assets/php/vendor/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 0000000..92868d3 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,144 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php new file mode 100644 index 0000000..cb43bb3 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php @@ -0,0 +1,113 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php new file mode 100644 index 0000000..9929eac --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php @@ -0,0 +1,103 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php new file mode 100644 index 0000000..157ab90 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php @@ -0,0 +1,93 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php new file mode 100644 index 0000000..1b9e589 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php @@ -0,0 +1,352 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/CookieTest.php b/assets/php/vendor/symfony/http-foundation/Tests/CookieTest.php new file mode 100644 index 0000000..070b7dd --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/CookieTest.php @@ -0,0 +1,223 @@ + + * + * 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 + * @author Hugo Hamon + * + * @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/assets/php/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php new file mode 100644 index 0000000..1152e46 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php @@ -0,0 +1,69 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/File/FakeFile.php b/assets/php/vendor/symfony/http-foundation/Tests/File/FakeFile.php new file mode 100644 index 0000000..c415989 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/File/FakeFile.php @@ -0,0 +1,45 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/File/FileTest.php b/assets/php/vendor/symfony/http-foundation/Tests/File/FileTest.php new file mode 100644 index 0000000..dbd9c44 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/File/FileTest.php @@ -0,0 +1,180 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension new file mode 100644 index 0000000..4d1ae35 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension @@ -0,0 +1 @@ +f \ No newline at end of file diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test new file mode 100644 index 0000000..b636f4b Binary files /dev/null and b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test differ diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif new file mode 100644 index 0000000..b636f4b Binary files /dev/null and b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif differ diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php b/assets/php/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php new file mode 100644 index 0000000..b3f1f02 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php @@ -0,0 +1,90 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php b/assets/php/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php new file mode 100644 index 0000000..36f122f --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php @@ -0,0 +1,273 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/FileBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/FileBagTest.php new file mode 100644 index 0000000..b1bbba0 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/FileBagTest.php @@ -0,0 +1,175 @@ + + * + * 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 + * @author Bulat Shakirzyanov + */ +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/assets/php/vendor/symfony/http-foundation/Tests/HeaderBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/HeaderBagTest.php new file mode 100644 index 0000000..6d19ceb --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/HeaderBagTest.php @@ -0,0 +1,205 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/IpUtilsTest.php b/assets/php/vendor/symfony/http-foundation/Tests/IpUtilsTest.php new file mode 100644 index 0000000..7a93f99 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/IpUtilsTest.php @@ -0,0 +1,104 @@ + + * + * 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:"JDatabaseDriverMysqli":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/assets/php/vendor/symfony/http-foundation/Tests/JsonResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/JsonResponseTest.php new file mode 100644 index 0000000..201839f --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/JsonResponseTest.php @@ -0,0 +1,257 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/ParameterBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ParameterBagTest.php new file mode 100644 index 0000000..ab908d8 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/ParameterBagTest.php @@ -0,0 +1,194 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php new file mode 100644 index 0000000..d389e83 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php @@ -0,0 +1,97 @@ + + * + * 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( + '##', + 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/assets/php/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php new file mode 100644 index 0000000..b5d8004 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php @@ -0,0 +1,151 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/RequestStackTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RequestStackTest.php new file mode 100644 index 0000000..a84fb26 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/RequestStackTest.php @@ -0,0 +1,70 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/RequestTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RequestTest.php new file mode 100644 index 0000000..230ad15 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/RequestTest.php @@ -0,0 +1,2329 @@ + + * + * 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:"JDatabaseDriverMysqli":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/assets/php/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php new file mode 100644 index 0000000..ce85535 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php @@ -0,0 +1,363 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/ResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTest.php new file mode 100644 index 0000000..350d972 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTest.php @@ -0,0 +1,1003 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/ResponseTestCase.php b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTestCase.php new file mode 100644 index 0000000..4ead34c --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTestCase.php @@ -0,0 +1,89 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/ServerBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ServerBagTest.php new file mode 100644 index 0000000..f8becec --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/ServerBagTest.php @@ -0,0 +1,170 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php new file mode 100644 index 0000000..724a0b9 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php @@ -0,0 +1,186 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php new file mode 100644 index 0000000..f074ce1 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php @@ -0,0 +1,182 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php new file mode 100644 index 0000000..fa8626a --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php @@ -0,0 +1,161 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php new file mode 100644 index 0000000..c4e75b1 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php @@ -0,0 +1,132 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/SessionTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/SessionTest.php new file mode 100644 index 0000000..41720e4 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/SessionTest.php @@ -0,0 +1,242 @@ + + * + * 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 + * @author Robert Schönthal + * @author Drak + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php new file mode 100644 index 0000000..3ac081e --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php @@ -0,0 +1,61 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc new file mode 100644 index 0000000..7a064c7 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc @@ -0,0 +1,151 @@ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected new file mode 100644 index 0000000..8203714 --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php new file mode 100644 index 0000000..3cfc125 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php @@ -0,0 +1,8 @@ + Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: max-age=10800, private, must-revalidate +) +shutdown diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php new file mode 100644 index 0000000..3e62fb9 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php @@ -0,0 +1,8 @@ + 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php new file mode 100644 index 0000000..a0f635c --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php @@ -0,0 +1,10 @@ + 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php new file mode 100644 index 0000000..96dca3c --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php @@ -0,0 +1,24 @@ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected new file mode 100644 index 0000000..33da0a5 --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php new file mode 100644 index 0000000..ffb5b20 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php @@ -0,0 +1,8 @@ + 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php new file mode 100644 index 0000000..ec51193 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php @@ -0,0 +1,13 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php new file mode 100644 index 0000000..2e7be35 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -0,0 +1,139 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php new file mode 100644 index 0000000..da05109 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -0,0 +1,333 @@ + + * + * 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 + * @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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php new file mode 100644 index 0000000..a6264e5 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php @@ -0,0 +1,77 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php new file mode 100644 index 0000000..4a9fb60 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php @@ -0,0 +1,38 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php new file mode 100644 index 0000000..718fd0f --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php @@ -0,0 +1,59 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php new file mode 100644 index 0000000..0a0e449 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -0,0 +1,411 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php new file mode 100644 index 0000000..b02c41a --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php @@ -0,0 +1,189 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php new file mode 100644 index 0000000..898a7d1 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php @@ -0,0 +1,97 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php new file mode 100644 index 0000000..69cf616 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php @@ -0,0 +1,139 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php new file mode 100644 index 0000000..82df554 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php @@ -0,0 +1,131 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php new file mode 100644 index 0000000..53accd3 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php @@ -0,0 +1,127 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php new file mode 100644 index 0000000..8fb8b42 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -0,0 +1,277 @@ + + * + * 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 + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php new file mode 100644 index 0000000..958dc0b --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php @@ -0,0 +1,96 @@ + + * + * 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 + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php new file mode 100644 index 0000000..cbb291f --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php @@ -0,0 +1,113 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php new file mode 100644 index 0000000..ed4fee6 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php @@ -0,0 +1,38 @@ + + * + * 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 + */ +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/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php new file mode 100644 index 0000000..6828253 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -0,0 +1,124 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php new file mode 100644 index 0000000..c2ded99 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php @@ -0,0 +1,126 @@ + + * + * 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/assets/php/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng b/assets/php/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng new file mode 100644 index 0000000..73708ca --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/php/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng b/assets/php/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng new file mode 100644 index 0000000..b9c3ca9 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + uri + + + + rfc + + + (rfc|bcp|std)\d+ + + + + + rfc-errata + + + + draft + + + (draft|RFC)(-[a-zA-Z0-9]+)+ + + + + + registry + + + + person + + + + text + + + note + + + + unicode + + + ucd\d+\.\d+\.\d+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (\d+|0x[\da-fA-F]+)(\s*-\s*(\d+|0x[\da-fA-F]+))? + + + + + + + + + + + + + 0x[0-9]{8} + + + + + + [0-1]+ + + + + + + + + + + + + + + + + + + + + + + legacy + mib + template + json + + + + + + + + + + diff --git a/assets/php/vendor/symfony/http-foundation/composer.json b/assets/php/vendor/symfony/http-foundation/composer.json new file mode 100644 index 0000000..f6c6f2e --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/http-foundation/phpunit.xml.dist b/assets/php/vendor/symfony/http-foundation/phpunit.xml.dist new file mode 100644 index 0000000..c1d61f8 --- /dev/null +++ b/assets/php/vendor/symfony/http-foundation/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/assets/php/vendor/symfony/polyfill-mbstring/LICENSE b/assets/php/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..24fa32c --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/polyfill-mbstring/Mbstring.php b/assets/php/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..67cf9ab --- /dev/null +++ b/assets/php/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,791 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/polyfill-mbstring/README.md b/assets/php/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..342e828 --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..3ca1641 --- /dev/null +++ b/assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1101 @@ + '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', + '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', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', +); + +$result =& $data; +unset($data); + +return $result; diff --git a/assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php new file mode 100644 index 0000000..ec94221 --- /dev/null +++ b/assets/php/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php @@ -0,0 +1,1109 @@ + '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Ž', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + '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', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', +); + +$result =& $data; +unset($data); + +return $result; diff --git a/assets/php/vendor/symfony/polyfill-mbstring/bootstrap.php b/assets/php/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..2fdcc5a --- /dev/null +++ b/assets/php/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,58 @@ + + * + * 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/assets/php/vendor/symfony/polyfill-mbstring/composer.json b/assets/php/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..4febcdd --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/polyfill-php70/LICENSE b/assets/php/vendor/symfony/polyfill-php70/LICENSE new file mode 100644 index 0000000..24fa32c --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/polyfill-php70/Php70.php b/assets/php/vendor/symfony/polyfill-php70/Php70.php new file mode 100644 index 0000000..8e78450 --- /dev/null +++ b/assets/php/vendor/symfony/polyfill-php70/Php70.php @@ -0,0 +1,74 @@ + + * + * 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 + * + * @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/assets/php/vendor/symfony/polyfill-php70/README.md b/assets/php/vendor/symfony/polyfill-php70/README.md new file mode 100644 index 0000000..04988c6 --- /dev/null +++ b/assets/php/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/assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php new file mode 100644 index 0000000..6819124 --- /dev/null +++ b/assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php @@ -0,0 +1,5 @@ + + * + * 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/assets/php/vendor/symfony/polyfill-php70/composer.json b/assets/php/vendor/symfony/polyfill-php70/composer.json new file mode 100644 index 0000000..88ff357 --- /dev/null +++ b/assets/php/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" + } + } +} diff --git a/assets/php/vendor/symfony/routing/.gitignore b/assets/php/vendor/symfony/routing/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/assets/php/vendor/symfony/routing/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/assets/php/vendor/symfony/routing/Annotation/Route.php b/assets/php/vendor/symfony/routing/Annotation/Route.php new file mode 100644 index 0000000..5b3cbea --- /dev/null +++ b/assets/php/vendor/symfony/routing/Annotation/Route.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Annotation; + +/** + * Annotation class for @Route(). + * + * @Annotation + * @Target({"CLASS", "METHOD"}) + * + * @author Fabien Potencier + */ +class Route +{ + private $path; + private $name; + private $requirements = array(); + private $options = array(); + private $defaults = array(); + private $host; + private $methods = array(); + private $schemes = array(); + private $condition; + + /** + * @param array $data An array of key/value parameters + * + * @throws \BadMethodCallException + */ + public function __construct(array $data) + { + if (isset($data['value'])) { + $data['path'] = $data['value']; + unset($data['value']); + } + + foreach ($data as $key => $value) { + $method = 'set'.str_replace('_', '', $key); + if (!method_exists($this, $method)) { + throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, get_class($this))); + } + $this->$method($value); + } + } + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + public function setHost($pattern) + { + $this->host = $pattern; + } + + public function getHost() + { + return $this->host; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setRequirements($requirements) + { + $this->requirements = $requirements; + } + + public function getRequirements() + { + return $this->requirements; + } + + public function setOptions($options) + { + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } + + public function setDefaults($defaults) + { + $this->defaults = $defaults; + } + + public function getDefaults() + { + return $this->defaults; + } + + public function setSchemes($schemes) + { + $this->schemes = is_array($schemes) ? $schemes : array($schemes); + } + + public function getSchemes() + { + return $this->schemes; + } + + public function setMethods($methods) + { + $this->methods = is_array($methods) ? $methods : array($methods); + } + + public function getMethods() + { + return $this->methods; + } + + public function setCondition($condition) + { + $this->condition = $condition; + } + + public function getCondition() + { + return $this->condition; + } +} diff --git a/assets/php/vendor/symfony/routing/CHANGELOG.md b/assets/php/vendor/symfony/routing/CHANGELOG.md new file mode 100644 index 0000000..e278f8b --- /dev/null +++ b/assets/php/vendor/symfony/routing/CHANGELOG.md @@ -0,0 +1,228 @@ +CHANGELOG +========= + +3.4.0 +----- + + * Added `NoConfigurationException`. + * Added the possibility to define a prefix for all routes of a controller via @Route(name="prefix_") + * Added support for prioritized routing loaders. + * Add matched and default parameters to redirect responses + * Added support for a `controller` keyword for configuring route controllers in YAML and XML configurations. + +3.3.0 +----- + + * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0. + * router.options.generator_class + * router.options.generator_base_class + * router.options.generator_dumper_class + * router.options.matcher_class + * router.options.matcher_base_class + * router.options.matcher_dumper_class + * router.options.matcher.cache_class + * router.options.generator.cache_class + +3.2.0 +----- + + * Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations. + * Added support for UTF-8 requirements + +2.8.0 +----- + + * allowed specifying a directory to recursively load all routing configuration files it contains + * Added ObjectRouteLoader and ServiceRouteLoader that allow routes to be loaded + by calling a method on an object/service. + * [DEPRECATION] Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method. + Use the constants defined in the `UrlGeneratorInterface` instead. + + Before: + + ```php + $router->generate('blog_show', array('slug' => 'my-blog-post'), true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + ``` + +2.5.0 +----- + + * [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and + will be removed in Symfony 3.0, since the performance gains were minimal and + it's hard to replicate the behaviour of PHP implementation. + +2.3.0 +----- + + * added RequestContext::getQueryString() + +2.2.0 +----- + + * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0): + + * The `pattern` setting for a route has been deprecated in favor of `path` + * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings + + Before: + + ```yaml + article_edit: + pattern: /article/{id} + requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' } + ``` + + ```xml + + POST|PUT + https + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPattern('/article/{id}'); + $route->setRequirement('_method', 'POST|PUT'); + $route->setRequirement('_scheme', 'https'); + ``` + + After: + + ```yaml + article_edit: + path: /article/{id} + methods: [POST, PUT] + schemes: https + requirements: { 'id': '\d+' } + ``` + + ```xml + + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPath('/article/{id}'); + $route->setMethods(array('POST', 'PUT')); + $route->setSchemes('https'); + ``` + + * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as + a flat array of Routes. So when using PHP to build the RouteCollection, you must + make sure to add routes to the sub-collection before adding it to the parent + collection (this is not relevant when using YAML or XML for Route definitions). + + Before: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $rootCollection->addCollection($subCollection); + $subCollection->add('foo', new Route('/foo')); + ``` + + After: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $subCollection->add('foo', new Route('/foo')); + $rootCollection->addCollection($subCollection); + ``` + + Also one must call `addCollection` from the bottom to the top hierarchy. + So the correct sequence is the following (and not the reverse): + + ```php + $childCollection->addCollection($grandchildCollection); + $rootCollection->addCollection($childCollection); + ``` + + * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()` + have been deprecated and will be removed in Symfony 2.3. + * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements + or options without adding a prefix is not supported anymore. So if you called `addPrefix` + with an empty prefix or `/` only (both have no relevance), like + `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` + you need to use the new dedicated methods `addDefaults($defaultsArray)`, + `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead. + * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated + because adding options has nothing to do with adding a path prefix. If you want to add options + to all child routes of a RouteCollection, you can use `addOptions()`. + * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated + because it suggested that all routes in the collection would have this prefix, which is + not necessarily true. On top of that, since there is no tree structure anymore, this method + is also useless. Don't worry about performance, prefix optimization for matching is still done + in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities. + * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be + used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options` + will still work, but have been deprecated. The `addPrefix` method should be used for this + use-case instead. + Before: `$parentCollection->addCollection($collection, '/prefix', array(...), array(...))` + After: + ```php + $collection->addPrefix('/prefix', array(...), array(...)); + $parentCollection->addCollection($collection); + ``` + * added support for the method default argument values when defining a @Route + * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`. + * Characters that function as separator between placeholders are now whitelisted + to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`. + * [BC BREAK] The default requirement of a variable has been changed slightly. + Previously it disallowed the previous and the next char around a variable. Now + it disallows the slash (`/`) and the next char. Using the previous char added + no value and was problematic because the route `/index.{_format}` would be + matched by `/index.ht/ml`. + * The default requirement now uses possessive quantifiers when possible which + improves matching performance by up to 20% because it prevents backtracking + when it's not needed. + * The ConfigurableRequirementsInterface can now also be used to disable the requirements + check on URL generation completely by calling `setStrictRequirements(null)`. It + improves performance in production environment as you should know that params always + pass the requirements (otherwise it would break your link anyway). + * There is no restriction on the route name anymore. So non-alphanumeric characters + are now also allowed. + * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static + (only relevant if you implemented your own RouteCompiler). + * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g. + "../parent-file" and "//example.com/dir/file". The third parameter in + `UrlGeneratorInterface::generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)` + now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for + claritiy. The old method calls with a Boolean parameter will continue to work because they + equal the signature using the constants. + +2.1.0 +----- + + * added RequestMatcherInterface + * added RequestContext::fromRequest() + * the UrlMatcher does not throw a \LogicException anymore when the required + scheme is not the current one + * added TraceableUrlMatcher + * added the possibility to define options, default values and requirements + for placeholders in prefix, including imported routes + * added RouterInterface::getRouteCollection + * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they + were decoded twice before. Note that the `urldecode()` calls have been + changed for a single `rawurldecode()` in order to support `+` for input + paths. + * added RouteCollection::getRoot method to retrieve the root of a + RouteCollection tree + * [BC BREAK] made RouteCollection::setParent private which could not have + been used anyway without creating inconsistencies + * [BC BREAK] RouteCollection::remove also removes a route from parent + collections (not only from its children) + * added ConfigurableRequirementsInterface that allows to disable exceptions + (and generate empty URLs instead) when generating a route with an invalid + parameter value diff --git a/assets/php/vendor/symfony/routing/CompiledRoute.php b/assets/php/vendor/symfony/routing/CompiledRoute.php new file mode 100644 index 0000000..8ecf515 --- /dev/null +++ b/assets/php/vendor/symfony/routing/CompiledRoute.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * CompiledRoutes are returned by the RouteCompiler class. + * + * @author Fabien Potencier + */ +class CompiledRoute implements \Serializable +{ + private $variables; + private $tokens; + private $staticPrefix; + private $regex; + private $pathVariables; + private $hostVariables; + private $hostRegex; + private $hostTokens; + + /** + * @param string $staticPrefix The static prefix of the compiled route + * @param string $regex The regular expression to use to match this route + * @param array $tokens An array of tokens to use to generate URL for this route + * @param array $pathVariables An array of path variables + * @param string|null $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + */ + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) + { + $this->staticPrefix = (string) $staticPrefix; + $this->regex = $regex; + $this->tokens = $tokens; + $this->pathVariables = $pathVariables; + $this->hostRegex = $hostRegex; + $this->hostTokens = $hostTokens; + $this->hostVariables = $hostVariables; + $this->variables = $variables; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'vars' => $this->variables, + 'path_prefix' => $this->staticPrefix, + 'path_regex' => $this->regex, + 'path_tokens' => $this->tokens, + 'path_vars' => $this->pathVariables, + 'host_regex' => $this->hostRegex, + 'host_tokens' => $this->hostTokens, + 'host_vars' => $this->hostVariables, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $data = unserialize($serialized, array('allowed_classes' => false)); + } else { + $data = unserialize($serialized); + } + + $this->variables = $data['vars']; + $this->staticPrefix = $data['path_prefix']; + $this->regex = $data['path_regex']; + $this->tokens = $data['path_tokens']; + $this->pathVariables = $data['path_vars']; + $this->hostRegex = $data['host_regex']; + $this->hostTokens = $data['host_tokens']; + $this->hostVariables = $data['host_vars']; + } + + /** + * Returns the static prefix. + * + * @return string The static prefix + */ + public function getStaticPrefix() + { + return $this->staticPrefix; + } + + /** + * Returns the regex. + * + * @return string The regex + */ + public function getRegex() + { + return $this->regex; + } + + /** + * Returns the host regex. + * + * @return string|null The host regex or null + */ + public function getHostRegex() + { + return $this->hostRegex; + } + + /** + * Returns the tokens. + * + * @return array The tokens + */ + public function getTokens() + { + return $this->tokens; + } + + /** + * Returns the host tokens. + * + * @return array The tokens + */ + public function getHostTokens() + { + return $this->hostTokens; + } + + /** + * Returns the variables. + * + * @return array The variables + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Returns the path variables. + * + * @return array The variables + */ + public function getPathVariables() + { + return $this->pathVariables; + } + + /** + * Returns the host variables. + * + * @return array The variables + */ + public function getHostVariables() + { + return $this->hostVariables; + } +} diff --git a/assets/php/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php b/assets/php/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php new file mode 100644 index 0000000..4af0a5a --- /dev/null +++ b/assets/php/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\DependencyInjection; + +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; + +/** + * Adds tagged routing.loader services to routing.resolver service. + * + * @author Fabien Potencier + */ +class RoutingResolverPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + private $resolverServiceId; + private $loaderTag; + + public function __construct($resolverServiceId = 'routing.resolver', $loaderTag = 'routing.loader') + { + $this->resolverServiceId = $resolverServiceId; + $this->loaderTag = $loaderTag; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $definition = $container->getDefinition($this->resolverServiceId); + + foreach ($this->findAndSortTaggedServices($this->loaderTag, $container) as $id) { + $definition->addMethodCall('addLoader', array(new Reference($id))); + } + } +} diff --git a/assets/php/vendor/symfony/routing/Exception/ExceptionInterface.php b/assets/php/vendor/symfony/routing/Exception/ExceptionInterface.php new file mode 100644 index 0000000..db76362 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * ExceptionInterface. + * + * @author Alexandre Salomé + */ +interface ExceptionInterface +{ +} diff --git a/assets/php/vendor/symfony/routing/Exception/InvalidParameterException.php b/assets/php/vendor/symfony/routing/Exception/InvalidParameterException.php new file mode 100644 index 0000000..94d841f --- /dev/null +++ b/assets/php/vendor/symfony/routing/Exception/InvalidParameterException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a parameter is not valid. + * + * @author Alexandre Salomé + */ +class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/assets/php/vendor/symfony/routing/Exception/MethodNotAllowedException.php b/assets/php/vendor/symfony/routing/Exception/MethodNotAllowedException.php new file mode 100644 index 0000000..712412f --- /dev/null +++ b/assets/php/vendor/symfony/routing/Exception/MethodNotAllowedException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was found but the request method is not allowed. + * + * This exception should trigger an HTTP 405 response in your application code. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface +{ + protected $allowedMethods = array(); + + public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null) + { + $this->allowedMethods = array_map('strtoupper', $allowedMethods); + + parent::__construct($message, $code, $previous); + } + + /** + * Gets the allowed HTTP methods. + * + * @return array + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } +} diff --git a/assets/php/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php b/assets/php/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php new file mode 100644 index 0000000..57f3a40 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route cannot be generated because of missing + * mandatory parameters. + * + * @author Alexandre Salomé + */ +class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/assets/php/vendor/symfony/routing/Exception/NoConfigurationException.php b/assets/php/vendor/symfony/routing/Exception/NoConfigurationException.php new file mode 100644 index 0000000..333bc74 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Exception/NoConfigurationException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when no routes are configured. + * + * @author Yonel Ceruto + */ +class NoConfigurationException extends ResourceNotFoundException +{ +} diff --git a/assets/php/vendor/symfony/routing/Exception/ResourceNotFoundException.php b/assets/php/vendor/symfony/routing/Exception/ResourceNotFoundException.php new file mode 100644 index 0000000..ccbca15 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Exception/ResourceNotFoundException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was not found. + * + * This exception should trigger an HTTP 404 response in your application code. + * + * @author Kris Wallsmith + */ +class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/assets/php/vendor/symfony/routing/Exception/RouteNotFoundException.php b/assets/php/vendor/symfony/routing/Exception/RouteNotFoundException.php new file mode 100644 index 0000000..24ab0b4 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Exception/RouteNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route does not exist. + * + * @author Alexandre Salomé + */ +class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/assets/php/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/assets/php/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php new file mode 100644 index 0000000..dc97b7e --- /dev/null +++ b/assets/php/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +/** + * ConfigurableRequirementsInterface must be implemented by URL generators that + * can be configured whether an exception should be generated when the parameters + * do not match the requirements. It is also possible to disable the requirements + * check for URL generation completely. + * + * The possible configurations and use-cases: + * - setStrictRequirements(true): Throw an exception for mismatching requirements. This + * is mostly useful in development environment. + * - setStrictRequirements(false): Don't throw an exception but return null as URL for + * mismatching requirements and log the problem. Useful when you cannot control all + * params because they come from third party libs but don't want to have a 404 in + * production environment. It should log the mismatch so one can review it. + * - setStrictRequirements(null): Return the URL with the given parameters without + * checking the requirements at all. When generating a URL you should either trust + * your params or you validated them beforehand because otherwise it would break your + * link anyway. So in production environment you should know that params always pass + * the requirements. Thus this option allows to disable the check on URL generation for + * performance reasons (saving a preg_match for each requirement every time a URL is + * generated). + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface ConfigurableRequirementsInterface +{ + /** + * Enables or disables the exception on incorrect parameters. + * Passing null will deactivate the requirements check completely. + * + * @param bool|null $enabled + */ + public function setStrictRequirements($enabled); + + /** + * Returns whether to throw an exception on incorrect parameters. + * Null means the requirements check is deactivated completely. + * + * @return bool|null + */ + public function isStrictRequirements(); +} diff --git a/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php new file mode 100644 index 0000000..659c5ba --- /dev/null +++ b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumper is the base class for all built-in generator dumpers. + * + * @author Fabien Potencier + */ +abstract class GeneratorDumper implements GeneratorDumperInterface +{ + private $routes; + + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php new file mode 100644 index 0000000..fed3472 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumperInterface is the interface that all generator dumper classes must implement. + * + * @author Fabien Potencier + */ +interface GeneratorDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to generate a URL of such a route. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/assets/php/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php b/assets/php/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php new file mode 100644 index 0000000..60bdf1d --- /dev/null +++ b/assets/php/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +/** + * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class PhpGeneratorDumper extends GeneratorDumper +{ + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the generator class + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'class' => 'ProjectUrlGenerator', + 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + ), $options); + + return <<context = \$context; + \$this->logger = \$logger; + if (null === self::\$declaredRoutes) { + self::\$declaredRoutes = {$this->generateDeclaredRoutes()}; + } + } + +{$this->generateGenerateMethod()} +} + +EOF; + } + + /** + * Generates PHP code representing an array of defined routes + * together with the routes properties (e.g. requirements). + * + * @return string PHP code + */ + private function generateDeclaredRoutes() + { + $routes = "array(\n"; + foreach ($this->getRoutes()->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $properties = array(); + $properties[] = $compiledRoute->getVariables(); + $properties[] = $route->getDefaults(); + $properties[] = $route->getRequirements(); + $properties[] = $compiledRoute->getTokens(); + $properties[] = $compiledRoute->getHostTokens(); + $properties[] = $route->getSchemes(); + + $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true))); + } + $routes .= ' )'; + + return $routes; + } + + /** + * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface. + * + * @return string PHP code + */ + private function generateGenerateMethod() + { + return <<<'EOF' + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (!isset(self::$declaredRoutes[$name])) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name]; + + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); + } +EOF; + } +} diff --git a/assets/php/vendor/symfony/routing/Generator/UrlGenerator.php b/assets/php/vendor/symfony/routing/Generator/UrlGenerator.php new file mode 100644 index 0000000..02a59a9 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Generator/UrlGenerator.php @@ -0,0 +1,321 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Psr\Log\LoggerInterface; + +/** + * UrlGenerator can generate a URL or a path for any route in the RouteCollection + * based on the passed parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface +{ + protected $routes; + protected $context; + + /** + * @var bool|null + */ + protected $strictRequirements = true; + + protected $logger; + + /** + * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. + * + * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars + * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. + * "?" and "#" (would be interpreted wrongly as query and fragment identifier), + * "'" and """ (are used as delimiters in HTML). + */ + protected $decodedChars = array( + // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning + // some webservers don't allow the slash in encoded form in the path for security reasons anyway + // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss + '%2F' => '/', + // the following chars are general delimiters in the URI specification but have only special meaning in the authority component + // so they can safely be used in the path in unencoded form + '%40' => '@', + '%3A' => ':', + // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally + // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + ); + + public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) + { + $this->routes = $routes; + $this->context = $context; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function setStrictRequirements($enabled) + { + $this->strictRequirements = null === $enabled ? null : (bool) $enabled; + } + + /** + * {@inheritdoc} + */ + public function isStrictRequirements() + { + return $this->strictRequirements; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (null === $route = $this->routes->get($name)) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + // the Route has a cache of its own and is not recompiled as long as it does not get modified + $compiledRoute = $route->compile(); + + return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); + } + + /** + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array()) + { + $variables = array_flip($variables); + $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); + + // all params must be given + if ($diff = array_diff_key($variables, $mergedParams)) { + throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name)); + } + + $url = ''; + $optional = true; + $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.'; + foreach ($tokens as $token) { + if ('variable' === $token[0]) { + if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + // check requirement + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) { + if ($this->strictRequirements) { + throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]))); + } + + if ($this->logger) { + $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]])); + } + + return; + } + + $url = $token[1].$mergedParams[$token[3]].$url; + $optional = false; + } + } else { + // static text + $url = $token[1].$url; + $optional = false; + } + } + + if ('' === $url) { + $url = '/'; + } + + // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = strtr(rawurlencode($url), $this->decodedChars); + + // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 + // so we need to encode them as they are not used for this purpose here + // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route + $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/')); + if ('/..' === substr($url, -3)) { + $url = substr($url, 0, -2).'%2E%2E'; + } elseif ('/.' === substr($url, -2)) { + $url = substr($url, 0, -1).'%2E'; + } + + $schemeAuthority = ''; + $host = $this->context->getHost(); + $scheme = $this->context->getScheme(); + + if ($requiredSchemes) { + if (!in_array($scheme, $requiredSchemes, true)) { + $referenceType = self::ABSOLUTE_URL; + $scheme = current($requiredSchemes); + } + } + + if ($hostTokens) { + $routeHost = ''; + foreach ($hostTokens as $token) { + if ('variable' === $token[0]) { + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) { + if ($this->strictRequirements) { + throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]))); + } + + if ($this->logger) { + $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]])); + } + + return; + } + + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; + } else { + $routeHost = $token[1].$routeHost; + } + } + + if ($routeHost !== $host) { + $host = $routeHost; + if (self::ABSOLUTE_URL !== $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + } + + if ((self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) && !empty($host)) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } + + if (self::RELATIVE_PATH === $referenceType) { + $url = self::getRelativePath($this->context->getPathInfo(), $url); + } else { + $url = $schemeAuthority.$this->context->getBaseUrl().$url; + } + + // add a query string if needed + $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, function ($a, $b) { + return $a == $b ? 0 : 1; + }); + + // extract fragment + $fragment = ''; + if (isset($defaults['_fragment'])) { + $fragment = $defaults['_fragment']; + } + + if (isset($extra['_fragment'])) { + $fragment = $extra['_fragment']; + unset($extra['_fragment']); + } + + if ($extra && $query = http_build_query($extra, '', '&', PHP_QUERY_RFC3986)) { + // "/" and "?" can be left decoded for better user experience, see + // http://tools.ietf.org/html/rfc3986#section-3.4 + $url .= '?'.strtr($query, array('%2F' => '/')); + } + + if ('' !== $fragment) { + $url .= '#'.strtr(rawurlencode($fragment), array('%2F' => '/', '%3F' => '?')); + } + + return $url; + } + + /** + * Returns the target path as relative reference from the base path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. + * 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 $basePath The base path + * @param string $targetPath The target path + * + * @return string The relative target path + */ + public static function getRelativePath($basePath, $targetPath) + { + if ($basePath === $targetPath) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); + 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 '' === $path || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } +} diff --git a/assets/php/vendor/symfony/routing/Generator/UrlGeneratorInterface.php b/assets/php/vendor/symfony/routing/Generator/UrlGeneratorInterface.php new file mode 100644 index 0000000..d6e7938 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Generator/UrlGeneratorInterface.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlGeneratorInterface is the interface that all URL generator classes must implement. + * + * The constants in this interface define the different types of resource references that + * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986 + * We are using the term "URL" instead of "URI" as this is more common in web applications + * and we do not need to distinguish them as the difference is mostly semantical and + * less technical. Generating URIs, i.e. representation-independent resource identifiers, + * is also possible. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface UrlGeneratorInterface extends RequestContextAwareInterface +{ + /** + * Generates an absolute URL, e.g. "http://example.com/dir/file". + */ + const ABSOLUTE_URL = 0; + + /** + * Generates an absolute path, e.g. "/dir/file". + */ + const ABSOLUTE_PATH = 1; + + /** + * Generates a relative path based on the current request path, e.g. "../parent-file". + * + * @see UrlGenerator::getRelativePath() + */ + const RELATIVE_PATH = 2; + + /** + * Generates a network path, e.g. "//example.com/dir/file". + * Such reference reuses the current scheme but specifies the host. + */ + const NETWORK_PATH = 3; + + /** + * Generates a URL or path for a specific route based on the given parameters. + * + * Parameters that reference placeholders in the route pattern will substitute them in the + * path or host. Extra params are added as query string to the URL. + * + * When the passed reference type cannot be generated for the route because it requires a different + * host or scheme than the current one, the method will return a more comprehensive reference + * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH + * but the route requires the https scheme whereas the current scheme is http, it will instead return an + * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches + * the route in any case. + * + * If there is no route with the given name, the generator must throw the RouteNotFoundException. + * + * The special parameter _fragment will be used as the document fragment suffixed to the final URL. + * + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param int $referenceType The type of reference to be generated (one of the constants) + * + * @return string The generated URL + * + * @throws RouteNotFoundException If the named route doesn't exist + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH); +} diff --git a/assets/php/vendor/symfony/routing/LICENSE b/assets/php/vendor/symfony/routing/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/assets/php/vendor/symfony/routing/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/assets/php/vendor/symfony/routing/Loader/AnnotationClassLoader.php b/assets/php/vendor/symfony/routing/Loader/AnnotationClassLoader.php new file mode 100644 index 0000000..2fe6fb5 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/AnnotationClassLoader.php @@ -0,0 +1,269 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Loader\LoaderResolverInterface; + +/** + * AnnotationClassLoader loads routing information from a PHP class and its methods. + * + * You need to define an implementation for the getRouteDefaults() method. Most of the + * time, this method should define some PHP callable to be called for the route + * (a controller in MVC speak). + * + * The @Route annotation can be set on the class (for global parameters), + * and on each method. + * + * The @Route annotation main value is the route path. The annotation also + * recognizes several parameters: requirements, options, defaults, schemes, + * methods, host, and name. The name parameter is mandatory. + * Here is an example of how you should be able to use it: + * + * /** + * * @Route("/Blog") + * * / + * class Blog + * { + * /** + * * @Route("/", name="blog_index") + * * / + * public function index() + * { + * } + * + * /** + * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) + * * / + * public function show() + * { + * } + * } + * + * @author Fabien Potencier + */ +abstract class AnnotationClassLoader implements LoaderInterface +{ + protected $reader; + + /** + * @var string + */ + protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; + + /** + * @var int + */ + protected $defaultRouteIndex = 0; + + public function __construct(Reader $reader) + { + $this->reader = $reader; + } + + /** + * Sets the annotation class to read route properties from. + * + * @param string $class A fully-qualified class name + */ + public function setRouteAnnotationClass($class) + { + $this->routeAnnotationClass = $class; + } + + /** + * Loads from annotations from a class. + * + * @param string $class A class name + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When route can't be parsed + */ + public function load($class, $type = null) + { + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + $class = new \ReflectionClass($class); + if ($class->isAbstract()) { + throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName())); + } + + $globals = $this->getGlobals($class); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($class->getFileName())); + + foreach ($class->getMethods() as $method) { + $this->defaultRouteIndex = 0; + foreach ($this->reader->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $this->routeAnnotationClass) { + $this->addRoute($collection, $annot, $globals, $class, $method); + } + } + } + + if (0 === $collection->count() && $class->hasMethod('__invoke') && $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + $globals['path'] = ''; + $globals['name'] = ''; + $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); + } + + return $collection; + } + + protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) + { + $name = $annot->getName(); + if (null === $name) { + $name = $this->getDefaultRouteName($class, $method); + } + $name = $globals['name'].$name; + + $defaults = array_replace($globals['defaults'], $annot->getDefaults()); + foreach ($method->getParameters() as $param) { + if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) { + $defaults[$param->getName()] = $param->getDefaultValue(); + } + } + $requirements = array_replace($globals['requirements'], $annot->getRequirements()); + $options = array_replace($globals['options'], $annot->getOptions()); + $schemes = array_merge($globals['schemes'], $annot->getSchemes()); + $methods = array_merge($globals['methods'], $annot->getMethods()); + + $host = $annot->getHost(); + if (null === $host) { + $host = $globals['host']; + } + + $condition = $annot->getCondition(); + if (null === $condition) { + $condition = $globals['condition']; + } + + $route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $this->configureRoute($route, $class, $method, $annot); + + $collection->add($name, $route); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); + } + + /** + * {@inheritdoc} + */ + public function setResolver(LoaderResolverInterface $resolver) + { + } + + /** + * {@inheritdoc} + */ + public function getResolver() + { + } + + /** + * Gets the default route name for a class method. + * + * @param \ReflectionClass $class + * @param \ReflectionMethod $method + * + * @return string + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) + { + $name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name); + if ($this->defaultRouteIndex > 0) { + $name .= '_'.$this->defaultRouteIndex; + } + ++$this->defaultRouteIndex; + + return $name; + } + + protected function getGlobals(\ReflectionClass $class) + { + $globals = array( + 'path' => '', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'host' => '', + 'condition' => '', + 'name' => '', + ); + + if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + if (null !== $annot->getName()) { + $globals['name'] = $annot->getName(); + } + + if (null !== $annot->getPath()) { + $globals['path'] = $annot->getPath(); + } + + if (null !== $annot->getRequirements()) { + $globals['requirements'] = $annot->getRequirements(); + } + + if (null !== $annot->getOptions()) { + $globals['options'] = $annot->getOptions(); + } + + if (null !== $annot->getDefaults()) { + $globals['defaults'] = $annot->getDefaults(); + } + + if (null !== $annot->getSchemes()) { + $globals['schemes'] = $annot->getSchemes(); + } + + if (null !== $annot->getMethods()) { + $globals['methods'] = $annot->getMethods(); + } + + if (null !== $annot->getHost()) { + $globals['host'] = $annot->getHost(); + } + + if (null !== $annot->getCondition()) { + $globals['condition'] = $annot->getCondition(); + } + } + + return $globals; + } + + protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition) + { + return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + } + + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); +} diff --git a/assets/php/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php b/assets/php/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php new file mode 100644 index 0000000..4574a02 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\DirectoryResource; + +/** + * AnnotationDirectoryLoader loads routing information from annotations set + * on PHP classes and methods. + * + * @author Fabien Potencier + */ +class AnnotationDirectoryLoader extends AnnotationFileLoader +{ + /** + * Loads from annotations from a directory. + * + * @param string $path A directory path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed + */ + public function load($path, $type = null) + { + if (!is_dir($dir = $this->locator->locate($path))) { + return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection(); + } + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($dir, '/\.php$/')); + $files = iterator_to_array(new \RecursiveIteratorIterator( + new \RecursiveCallbackFilterIterator( + new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + function (\SplFileInfo $current) { + return '.' !== substr($current->getBasename(), 0, 1); + } + ), + \RecursiveIteratorIterator::LEAVES_ONLY + )); + usort($files, function (\SplFileInfo $a, \SplFileInfo $b) { + return (string) $a > (string) $b ? 1 : -1; + }); + + foreach ($files as $file) { + if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) { + continue; + } + + if ($class = $this->findClass($file)) { + $refl = new \ReflectionClass($class); + if ($refl->isAbstract()) { + continue; + } + + $collection->addCollection($this->loader->load($class, $type)); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + if ('annotation' === $type) { + return true; + } + + if ($type || !is_string($resource)) { + return false; + } + + try { + return is_dir($this->locator->locate($resource)); + } catch (\Exception $e) { + return false; + } + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/AnnotationFileLoader.php b/assets/php/vendor/symfony/routing/Loader/AnnotationFileLoader.php new file mode 100644 index 0000000..cf9f070 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/AnnotationFileLoader.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\FileLocatorInterface; + +/** + * AnnotationFileLoader loads routing information from annotations set + * on a PHP class and its methods. + * + * @author Fabien Potencier + */ +class AnnotationFileLoader extends FileLoader +{ + protected $loader; + + /** + * @throws \RuntimeException + */ + public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader) + { + if (!function_exists('token_get_all')) { + throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.'); + } + + parent::__construct($locator); + + $this->loader = $loader; + } + + /** + * Loads from annotations from a file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + if ($class = $this->findClass($path)) { + $collection->addResource(new FileResource($path)); + $collection->addCollection($this->loader->load($class, $type)); + } + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + gc_mem_caches(); + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); + } + + /** + * Returns the full class name for the first class in the file. + * + * @param string $file A PHP file path + * + * @return string|false Full class name if found, false otherwise + */ + protected function findClass($file) + { + $class = false; + $namespace = false; + $tokens = token_get_all(file_get_contents($file)); + + if (1 === count($tokens) && T_INLINE_HTML === $tokens[0][0]) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forgot to add the " 0; --$j) { + if (!isset($tokens[$j][1])) { + break; + } + + if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) { + $skipClassToken = true; + break; + } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { + break; + } + } + + if (!$skipClassToken) { + $class = true; + } + } + + if (T_NAMESPACE === $token[0]) { + $namespace = true; + } + } + + return false; + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/ClosureLoader.php b/assets/php/vendor/symfony/routing/Loader/ClosureLoader.php new file mode 100644 index 0000000..5df9f6a --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/ClosureLoader.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Routing\RouteCollection; + +/** + * ClosureLoader loads routes from a PHP closure. + * + * The Closure must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class ClosureLoader extends Loader +{ + /** + * Loads a Closure. + * + * @param \Closure $closure A Closure + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($closure, $type = null) + { + return $closure(); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return $resource instanceof \Closure && (!$type || 'closure' === $type); + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php new file mode 100644 index 0000000..8baefdd --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class CollectionConfigurator +{ + use Traits\AddTrait; + use Traits\RouteTrait; + + private $parent; + private $parentConfigurator; + + public function __construct(RouteCollection $parent, $name, self $parentConfigurator = null) + { + $this->parent = $parent; + $this->name = $name; + $this->collection = new RouteCollection(); + $this->route = new Route(''); + $this->parentConfigurator = $parentConfigurator; // for GC control + } + + public function __destruct() + { + $this->collection->addPrefix(rtrim($this->route->getPath(), '/')); + $this->parent->addCollection($this->collection); + } + + /** + * Adds a route. + * + * @param string $name + * @param string $path + * + * @return RouteConfigurator + */ + final public function add($name, $path) + { + $this->collection->add($this->name.$name, $route = clone $this->route); + + return new RouteConfigurator($this->collection, $route->setPath($path), $this->name, $this); + } + + /** + * Creates a sub-collection. + * + * @return self + */ + final public function collection($name = '') + { + return new self($this->collection, $this->name.$name, $this); + } + + /** + * Sets the prefix to add to the path of all child routes. + * + * @param string $prefix + * + * @return $this + */ + final public function prefix($prefix) + { + $this->route->setPath($prefix); + + return $this; + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php new file mode 100644 index 0000000..d0a3c37 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class ImportConfigurator +{ + use Traits\RouteTrait; + + private $parent; + + public function __construct(RouteCollection $parent, RouteCollection $route) + { + $this->parent = $parent; + $this->route = $route; + } + + public function __destruct() + { + $this->parent->addCollection($this->route); + } + + /** + * Sets the prefix to add to the path of all child routes. + * + * @param string $prefix + * + * @return $this + */ + final public function prefix($prefix) + { + $this->route->addPrefix($prefix); + + return $this; + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php new file mode 100644 index 0000000..6422bbf --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class RouteConfigurator +{ + use Traits\AddTrait; + use Traits\RouteTrait; + + private $parentConfigurator; + + public function __construct(RouteCollection $collection, Route $route, $name = '', CollectionConfigurator $parentConfigurator = null) + { + $this->collection = $collection; + $this->route = $route; + $this->name = $name; + $this->parentConfigurator = $parentConfigurator; // for GC control + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php new file mode 100644 index 0000000..d992cef --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\Routing\Loader\PhpFileLoader; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +class RoutingConfigurator +{ + use Traits\AddTrait; + + private $loader; + private $path; + private $file; + + public function __construct(RouteCollection $collection, PhpFileLoader $loader, $path, $file) + { + $this->collection = $collection; + $this->loader = $loader; + $this->path = $path; + $this->file = $file; + } + + /** + * @return ImportConfigurator + */ + final public function import($resource, $type = null, $ignoreErrors = false) + { + $this->loader->setCurrentDir(dirname($this->path)); + $imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file); + if (!is_array($imported)) { + return new ImportConfigurator($this->collection, $imported); + } + + $mergedCollection = new RouteCollection(); + foreach ($imported as $subCollection) { + $mergedCollection->addCollection($subCollection); + } + + return new ImportConfigurator($this->collection, $mergedCollection); + } + + /** + * @return CollectionConfigurator + */ + final public function collection($name = '') + { + return new CollectionConfigurator($this->collection, $name); + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php new file mode 100644 index 0000000..e8b8fa2 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +trait AddTrait +{ + /** + * @var RouteCollection + */ + private $collection; + + private $name = ''; + + /** + * Adds a route. + * + * @param string $name + * @param string $path + * + * @return RouteConfigurator + */ + final public function add($name, $path) + { + $parentConfigurator = $this instanceof RouteConfigurator ? $this->parentConfigurator : null; + $this->collection->add($this->name.$name, $route = new Route($path)); + + return new RouteConfigurator($this->collection, $route, '', $parentConfigurator); + } + + /** + * Adds a route. + * + * @param string $name + * @param string $path + * + * @return RouteConfigurator + */ + final public function __invoke($name, $path) + { + return $this->add($name, $path); + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php new file mode 100644 index 0000000..4d2e255 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +trait RouteTrait +{ + /** + * @var RouteCollection|Route + */ + private $route; + + /** + * Adds defaults. + * + * @return $this + */ + final public function defaults(array $defaults) + { + $this->route->addDefaults($defaults); + + return $this; + } + + /** + * Adds requirements. + * + * @return $this + */ + final public function requirements(array $requirements) + { + $this->route->addRequirements($requirements); + + return $this; + } + + /** + * Adds options. + * + * @return $this + */ + final public function options(array $options) + { + $this->route->addOptions($options); + + return $this; + } + + /** + * Sets the condition. + * + * @param string $condition + * + * @return $this + */ + final public function condition($condition) + { + $this->route->setCondition($condition); + + return $this; + } + + /** + * Sets the pattern for the host. + * + * @param string $pattern + * + * @return $this + */ + final public function host($pattern) + { + $this->route->setHost($pattern); + + return $this; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @param string[] $schemes + * + * @return $this + */ + final public function schemes(array $schemes) + { + $this->route->setSchemes($schemes); + + return $this; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * @param string[] $methods + * + * @return $this + */ + final public function methods(array $methods) + { + $this->route->setMethods($methods); + + return $this; + } + + /** + * Adds the "_controller" entry to defaults. + * + * @param callable|string $controller a callable or parseable pseudo-callable + * + * @return $this + */ + final public function controller($controller) + { + $this->route->addDefaults(array('_controller' => $controller)); + + return $this; + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php b/assets/php/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php new file mode 100644 index 0000000..6c16216 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Routing\Loader\ObjectRouteLoader; + +/** + * A route loader that executes a service to load the routes. + * + * This depends on the DependencyInjection component. + * + * @author Ryan Weaver + */ +class ServiceRouterLoader extends ObjectRouteLoader +{ + /** + * @var ContainerInterface + */ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getServiceObject($id) + { + return $this->container->get($id); + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/DirectoryLoader.php b/assets/php/vendor/symfony/routing/Loader/DirectoryLoader.php new file mode 100644 index 0000000..4bb5b31 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/DirectoryLoader.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\DirectoryResource; + +class DirectoryLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($path)); + + foreach (scandir($path) as $dir) { + if ('.' !== $dir[0]) { + $this->setCurrentDir($path); + $subPath = $path.'/'.$dir; + $subType = null; + + if (is_dir($subPath)) { + $subPath .= '/'; + $subType = 'directory'; + } + + $subCollection = $this->import($subPath, $subType, false, $path); + $collection->addCollection($subCollection); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + // only when type is forced to directory, not to conflict with AnnotationLoader + + return 'directory' === $type; + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/GlobFileLoader.php b/assets/php/vendor/symfony/routing/Loader/GlobFileLoader.php new file mode 100644 index 0000000..03ee341 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/GlobFileLoader.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Routing\RouteCollection; + +/** + * GlobFileLoader loads files from a glob pattern. + * + * @author Nicolas Grekas + */ +class GlobFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + $collection = new RouteCollection(); + + foreach ($this->glob($resource, false, $globResource) as $path => $info) { + $collection->addCollection($this->import($path)); + } + + $collection->addResource($globResource); + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return 'glob' === $type; + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/ObjectRouteLoader.php b/assets/php/vendor/symfony/routing/Loader/ObjectRouteLoader.php new file mode 100644 index 0000000..0899a81 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/ObjectRouteLoader.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * A route loader that calls a method on an object to load the routes. + * + * @author Ryan Weaver + */ +abstract class ObjectRouteLoader extends Loader +{ + /** + * Returns the object that the method will be called on to load routes. + * + * For example, if your application uses a service container, + * the $id may be a service id. + * + * @param string $id + * + * @return object + */ + abstract protected function getServiceObject($id); + + /** + * Calls the service that will load the routes. + * + * @param mixed $resource Some value that will resolve to a callable + * @param string|null $type The resource type + * + * @return RouteCollection + */ + public function load($resource, $type = null) + { + $parts = explode(':', $resource); + if (2 != count($parts)) { + throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource)); + } + + $serviceString = $parts[0]; + $method = $parts[1]; + + $loaderObject = $this->getServiceObject($serviceString); + + if (!is_object($loaderObject)) { + throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', get_class($this), gettype($loaderObject))); + } + + if (!method_exists($loaderObject, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, get_class($loaderObject), $resource)); + } + + $routeCollection = call_user_func(array($loaderObject, $method), $this); + + if (!$routeCollection instanceof RouteCollection) { + $type = is_object($routeCollection) ? get_class($routeCollection) : gettype($routeCollection); + + throw new \LogicException(sprintf('The %s::%s method must return a RouteCollection: %s returned', get_class($loaderObject), $method, $type)); + } + + // make the service file tracked so that if it changes, the cache rebuilds + $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); + + return $routeCollection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return 'service' === $type; + } + + private function addClassResource(\ReflectionClass $class, RouteCollection $collection) + { + do { + if (is_file($class->getFileName())) { + $collection->addResource(new FileResource($class->getFileName())); + } + } while ($class = $class->getParentClass()); + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/PhpFileLoader.php b/assets/php/vendor/symfony/routing/Loader/PhpFileLoader.php new file mode 100644 index 0000000..3fcd692 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/PhpFileLoader.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpFileLoader loads routes from a PHP file. + * + * The file must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + /** + * Loads a PHP file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + $this->setCurrentDir(dirname($path)); + + // the closure forbids access to the private scope in the included file + $loader = $this; + $load = \Closure::bind(function ($file) use ($loader) { + return include $file; + }, null, ProtectedPhpFileLoader::class); + + $result = $load($path); + + if ($result instanceof \Closure) { + $collection = new RouteCollection(); + $result(new RoutingConfigurator($collection, $this, $path, $file), $this); + } else { + $collection = $result; + } + + $collection->addResource(new FileResource($path)); + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type); + } +} + +/** + * @internal + */ +final class ProtectedPhpFileLoader extends PhpFileLoader +{ +} diff --git a/assets/php/vendor/symfony/routing/Loader/XmlFileLoader.php b/assets/php/vendor/symfony/routing/Loader/XmlFileLoader.php new file mode 100644 index 0000000..f3f6605 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/XmlFileLoader.php @@ -0,0 +1,359 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader loads XML routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class XmlFileLoader extends FileLoader +{ + const NAMESPACE_URI = 'http://symfony.com/schema/routing'; + const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be + * parsed because it does not validate against the scheme + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $xml = $this->loadFile($path); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // process routes and imports + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + + $this->parseNode($collection, $node, $path, $file); + } + + return $collection; + } + + /** + * Parses a node from a loaded XML file. + * + * @param RouteCollection $collection Collection to associate with the node + * @param \DOMElement $node Element to parse + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if (self::NAMESPACE_URI !== $node->namespaceURI) { + return; + } + + switch ($node->localName) { + case 'route': + $this->parseRoute($collection, $node, $path); + break; + case 'import': + $this->parseImport($collection, $node, $path, $file); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); + } + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path) + { + if ('' === ($id = $node->getAttribute('id')) || !$node->hasAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "path" attribute.', $path)); + } + + $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY); + $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY); + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); + $collection->add($id, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if ('' === $resource = $node->getAttribute('resource')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path)); + } + + $type = $node->getAttribute('type'); + $prefix = $node->getAttribute('prefix'); + $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null; + $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null; + $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null; + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + $this->setCurrentDir(dirname($path)); + + $imported = $this->import($resource, ('' !== $type ? $type : null), false, $file); + + if (!is_array($imported)) { + $imported = array($imported); + } + + foreach ($imported as $subCollection) { + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * + * @return \DOMDocument + * + * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors + * or when the XML structure is not as expected by the scheme - + * see validate() + */ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); + } + + /** + * Parses the config elements (default, requirement, option). + * + * @param \DOMElement $node Element to parse that contains the configs + * @param string $path Full path of the XML file being processed + * + * @return array An array with the defaults as first item, requirements as second and options as third + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseConfigs(\DOMElement $node, $path) + { + $defaults = array(); + $requirements = array(); + $options = array(); + $condition = null; + + foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { + if ($node !== $n->parentNode) { + continue; + } + + switch ($n->localName) { + case 'default': + if ($this->isElementValueNull($n)) { + $defaults[$n->getAttribute('key')] = null; + } else { + $defaults[$n->getAttribute('key')] = $this->parseDefaultsConfig($n, $path); + } + + break; + case 'requirement': + $requirements[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'option': + $options[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'condition': + $condition = trim($n->textContent); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path)); + } + } + + if ($controller = $node->getAttribute('controller')) { + if (isset($defaults['_controller'])) { + $name = $node->hasAttribute('id') ? sprintf('"%s"', $node->getAttribute('id')) : sprintf('the "%s" tag', $node->tagName); + + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for %s.', $path, $name)); + } + + $defaults['_controller'] = $controller; + } + + return array($defaults, $requirements, $options, $condition); + } + + /** + * Parses the "default" elements. + * + * @param \DOMElement $element The "default" element to parse + * @param string $path Full path of the XML file being processed + * + * @return array|bool|float|int|string|null The parsed value of the "default" element + */ + private function parseDefaultsConfig(\DOMElement $element, $path) + { + if ($this->isElementValueNull($element)) { + return; + } + + // Check for existing element nodes in the default element. There can + // only be a single element inside a default element. So this element + // (if one was found) can safely be returned. + foreach ($element->childNodes as $child) { + if (!$child instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $child->namespaceURI) { + continue; + } + + return $this->parseDefaultNode($child, $path); + } + + // If the default element doesn't contain a nested "bool", "int", "float", + // "string", "list", or "map" element, the element contents will be treated + // as the string value of the associated default option. + return trim($element->textContent); + } + + /** + * Recursively parses the value of a "default" element. + * + * @param \DOMElement $node The node value + * @param string $path Full path of the XML file being processed + * + * @return array|bool|float|int|string The parsed value + * + * @throws \InvalidArgumentException when the XML is invalid + */ + private function parseDefaultNode(\DOMElement $node, $path) + { + if ($this->isElementValueNull($node)) { + return; + } + + switch ($node->localName) { + case 'bool': + return 'true' === trim($node->nodeValue) || '1' === trim($node->nodeValue); + case 'int': + return (int) trim($node->nodeValue); + case 'float': + return (float) trim($node->nodeValue); + case 'string': + return trim($node->nodeValue); + case 'list': + $list = array(); + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $list[] = $this->parseDefaultNode($element, $path); + } + + return $list; + case 'map': + $map = array(); + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $map[$element->getAttribute('key')] = $this->parseDefaultNode($element, $path); + } + + return $map; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".', $node->localName, $path)); + } + } + + private function isElementValueNull(\DOMElement $element) + { + $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + + if (!$element->hasAttributeNS($namespaceUri, 'nil')) { + return false; + } + + return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil'); + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/YamlFileLoader.php b/assets/php/vendor/symfony/routing/Loader/YamlFileLoader.php new file mode 100644 index 0000000..f59f909 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/YamlFileLoader.php @@ -0,0 +1,233 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Config\Loader\FileLoader; + +/** + * YamlFileLoader loads Yaml routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class YamlFileLoader extends FileLoader +{ + private static $availableKeys = array( + 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', + ); + private $yamlParser; + + /** + * Loads a Yaml file. + * + * @param string $file A Yaml file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + if (!stream_is_local($path)) { + throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path)); + } + + if (!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path)); + } + + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + $prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) { + $message = E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message; + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false; + }); + + try { + $parsedConfig = $this->yamlParser->parseFile($path); + } catch (ParseException $e) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e); + } finally { + restore_error_handler(); + } + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // empty file + if (null === $parsedConfig) { + return $collection; + } + + // not an array + if (!is_array($parsedConfig)) { + throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path)); + } + + foreach ($parsedConfig as $name => $config) { + $this->validate($config, $name, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true) && (!$type || 'yaml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param string $name Route name + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + */ + protected function parseRoute(RouteCollection $collection, $name, array $config, $path) + { + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : ''; + $schemes = isset($config['schemes']) ? $config['schemes'] : array(); + $methods = isset($config['methods']) ? $config['methods'] : array(); + $condition = isset($config['condition']) ? $config['condition'] : null; + + if (isset($config['controller'])) { + $defaults['_controller'] = $config['controller']; + } + + $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $collection->add($name, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + * @param string $file Loaded file name + */ + protected function parseImport(RouteCollection $collection, array $config, $path, $file) + { + $type = isset($config['type']) ? $config['type'] : null; + $prefix = isset($config['prefix']) ? $config['prefix'] : ''; + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : null; + $condition = isset($config['condition']) ? $config['condition'] : null; + $schemes = isset($config['schemes']) ? $config['schemes'] : null; + $methods = isset($config['methods']) ? $config['methods'] : null; + + if (isset($config['controller'])) { + $defaults['_controller'] = $config['controller']; + } + + $this->setCurrentDir(dirname($path)); + + $imported = $this->import($config['resource'], $type, false, $file); + + if (!is_array($imported)) { + $imported = array($imported); + } + + foreach ($imported as $subCollection) { + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + } + + /** + * Validates the route configuration. + * + * @param array $config A resource config + * @param string $name The config key + * @param string $path The loaded file path + * + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + protected function validate($config, $name, $path) + { + if (!is_array($config)) { + throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); + } + if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', + $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys) + )); + } + if (isset($config['resource']) && isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', + $path, $name + )); + } + if (!isset($config['resource']) && isset($config['type'])) { + throw new \InvalidArgumentException(sprintf( + 'The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', + $name, $path + )); + } + if (!isset($config['resource']) && !isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'You must define a "path" for the route "%s" in file "%s".', + $name, $path + )); + } + if (isset($config['controller']) && isset($config['defaults']['_controller'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name)); + } + } +} diff --git a/assets/php/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd b/assets/php/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd new file mode 100644 index 0000000..a97111a --- /dev/null +++ b/assets/php/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php new file mode 100644 index 0000000..6916297 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Collection of routes. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperCollection implements \IteratorAggregate +{ + /** + * @var DumperCollection|null + */ + private $parent; + + /** + * @var DumperCollection[]|DumperRoute[] + */ + private $children = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * Returns the children routes and collections. + * + * @return self[]|DumperRoute[] + */ + public function all() + { + return $this->children; + } + + /** + * Adds a route or collection. + * + * @param DumperRoute|DumperCollection The route or collection + */ + public function add($child) + { + if ($child instanceof self) { + $child->setParent($this); + } + $this->children[] = $child; + } + + /** + * Sets children. + * + * @param array $children The children + */ + public function setAll(array $children) + { + foreach ($children as $child) { + if ($child instanceof self) { + $child->setParent($this); + } + } + $this->children = $children; + } + + /** + * Returns an iterator over the children. + * + * @return \Iterator|DumperCollection[]|DumperRoute[] The iterator + */ + public function getIterator() + { + return new \ArrayIterator($this->children); + } + + /** + * Returns the root of the collection. + * + * @return self The root collection + */ + public function getRoot() + { + return (null !== $this->parent) ? $this->parent->getRoot() : $this; + } + + /** + * Returns the parent collection. + * + * @return self|null The parent collection or null if the collection has no parent + */ + protected function getParent() + { + return $this->parent; + } + + /** + * Sets the parent collection. + */ + protected function setParent(DumperCollection $parent) + { + $this->parent = $parent; + } + + /** + * Returns true if the attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function hasAttribute($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * Returns an attribute by name. + * + * @param string $name The attribute name + * @param mixed $default Default value is the attribute doesn't exist + * + * @return mixed The attribute value + */ + public function getAttribute($name, $default = null) + { + return $this->hasAttribute($name) ? $this->attributes[$name] : $default; + } + + /** + * Sets an attribute by name. + * + * @param string $name The attribute name + * @param mixed $value The attribute value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * Sets multiple attributes. + * + * @param array $attributes The attributes + */ + public function setAttributes($attributes) + { + $this->attributes = $attributes; + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php new file mode 100644 index 0000000..c71989a --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; + +/** + * Container for a Route. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperRoute +{ + private $name; + private $route; + + /** + * @param string $name The route name + * @param Route $route The route + */ + public function __construct($name, Route $route) + { + $this->name = $name; + $this->route = $route; + } + + /** + * Returns the route name. + * + * @return string The route name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the route. + * + * @return Route The route + */ + public function getRoute() + { + return $this->route; + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php new file mode 100644 index 0000000..ea51ab4 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumper is the abstract class for all built-in matcher dumpers. + * + * @author Fabien Potencier + */ +abstract class MatcherDumper implements MatcherDumperInterface +{ + private $routes; + + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php new file mode 100644 index 0000000..5e7c134 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumperInterface is the interface that all matcher dumper classes must implement. + * + * @author Fabien Potencier + */ +interface MatcherDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to match a request against these routes. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php new file mode 100644 index 0000000..40d8df6 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php @@ -0,0 +1,429 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Arnaud Le Blanc + */ +class PhpMatcherDumper extends MatcherDumper +{ + private $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the matcher class + */ + public function dump(array $options = array()) + { + $options = array_replace(array( + 'class' => 'ProjectUrlMatcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ), $options); + + // trailing slash support is only enabled if we know how to redirect the user + $interfaces = class_implements($options['base_class']); + $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + + return <<context = \$context; + } + +{$this->generateMatchMethod($supportsRedirections)} +} + +EOF; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Generates the code for the match method implementing UrlMatcherInterface. + * + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string Match method as PHP code + */ + private function generateMatchMethod($supportsRedirections) + { + $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); + + return <<context; + \$request = \$this->request ?: \$this->createRequest(\$pathinfo); + \$requestMethod = \$canonicalMethod = \$context->getMethod(); + + if ('HEAD' === \$requestMethod) { + \$canonicalMethod = 'GET'; + } + +$code + + throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); + } +EOF; + } + + /** + * Generates PHP code to match a RouteCollection with all its routes. + * + * @param RouteCollection $routes A RouteCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string PHP code + */ + private function compileRoutes(RouteCollection $routes, $supportsRedirections) + { + $fetchedHost = false; + $groups = $this->groupRoutesByHostRegex($routes); + $code = ''; + + foreach ($groups as $collection) { + if (null !== $regex = $collection->getAttribute('host_regex')) { + if (!$fetchedHost) { + $code .= " \$host = \$context->getHost();\n\n"; + $fetchedHost = true; + } + + $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); + } + + $tree = $this->buildStaticPrefixCollection($collection); + $groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections); + + if (null !== $regex) { + // apply extra indention at each line (except empty ones) + $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); + $code .= $groupCode; + $code .= " }\n\n"; + } else { + $code .= $groupCode; + } + } + + // used to display the Welcome Page in apps that don't define a homepage + $code .= " if ('/' === \$pathinfo && !\$allow) {\n"; + $code .= " throw new Symfony\Component\Routing\Exception\NoConfigurationException();\n"; + $code .= " }\n"; + + return $code; + } + + private function buildStaticPrefixCollection(DumperCollection $collection) + { + $prefixCollection = new StaticPrefixCollection(); + + foreach ($collection as $dumperRoute) { + $prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix(); + $prefixCollection->addRoute($prefix, $dumperRoute); + } + + $prefixCollection->optimizeGroups(); + + return $prefixCollection; + } + + /** + * Generates PHP code to match a tree of routes. + * + * @param StaticPrefixCollection $collection A StaticPrefixCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string $ifOrElseIf either "if" or "elseif" to influence chaining + * + * @return string PHP code + */ + private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if') + { + $code = ''; + $prefix = $collection->getPrefix(); + + if (!empty($prefix) && '/' !== $prefix) { + $code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true)); + } + + $ifOrElseIf = 'if'; + + foreach ($collection->getItems() as $route) { + if ($route instanceof StaticPrefixCollection) { + $code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf); + $ifOrElseIf = 'elseif'; + } else { + $code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n"; + $ifOrElseIf = 'if'; + } + } + + if (!empty($prefix) && '/' !== $prefix) { + $code .= " }\n\n"; + // apply extra indention at each line (except empty ones) + $code = preg_replace('/^.{2,}$/m', ' $0', $code); + } + + return $code; + } + + /** + * Compiles a single Route to PHP code used to match it against the path info. + * + * @param Route $route A Route instance + * @param string $name The name of the Route + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code + * + * @return string PHP code + * + * @throws \LogicException + */ + private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) + { + $code = ''; + $compiledRoute = $route->compile(); + $conditions = array(); + $hasTrailingSlash = false; + $matches = false; + $hostMatches = false; + $methods = $route->getMethods(); + + $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('GET', $methods)); + $regex = $compiledRoute->getRegex(); + + if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#'.('u' === substr($regex, -1) ? 'u' : ''), $regex, $m)) { + if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) { + $conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); + $hasTrailingSlash = true; + } else { + $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true)); + } + } else { + if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { + $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true)); + } + + if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { + $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); + $hasTrailingSlash = true; + } + $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true)); + + $matches = true; + } + + if ($compiledRoute->getHostVariables()) { + $hostMatches = true; + } + + if ($route->getCondition()) { + $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request')); + } + + $conditions = implode(' && ', $conditions); + + $code .= <<mergeDefaults(array_replace(%s), %s);\n", + implode(', ', $vars), + str_replace("\n", '', var_export($route->getDefaults(), true)) + ); + } elseif ($route->getDefaults()) { + $code .= sprintf(" \$ret = %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); + } else { + $code .= sprintf(" \$ret = array('_route' => '%s');\n", $name); + } + + if ($hasTrailingSlash) { + $code .= <<redirect(\$rawPathinfo.'/', '$name')); + } + + +EOF; + } + + if ($methods) { + $methodVariable = in_array('GET', $methods) ? '$canonicalMethod' : '$requestMethod'; + $methods = implode("', '", $methods); + } + + if ($schemes = $route->getSchemes()) { + if (!$supportsRedirections) { + throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); + } + $schemes = str_replace("\n", '', var_export(array_flip($schemes), true)); + if ($methods) { + $code .= <<getScheme()]); + if (!in_array($methodVariable, array('$methods'))) { + if (\$hasRequiredScheme) { + \$allow = array_merge(\$allow, array('$methods')); + } + goto $gotoname; + } + if (!\$hasRequiredScheme) { + if ('GET' !== \$canonicalMethod) { + goto $gotoname; + } + + return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes))); + } + + +EOF; + } else { + $code .= <<getScheme()])) { + if ('GET' !== \$canonicalMethod) { + goto $gotoname; + } + + return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes))); + } + + +EOF; + } + } elseif ($methods) { + $code .= <<setAttribute('host_regex', null); + $groups->add($currentGroup); + + foreach ($routes as $name => $route) { + $hostRegex = $route->compile()->getHostRegex(); + if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', $hostRegex); + $groups->add($currentGroup); + } + $currentGroup->add(new DumperRoute($name, $route)); + } + + return $groups; + } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php new file mode 100644 index 0000000..7365808 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Prefix tree of routes preserving routes order. + * + * @author Frank de Jonge + * + * @internal + */ +class StaticPrefixCollection +{ + /** + * @var string + */ + private $prefix; + + /** + * @var array[]|StaticPrefixCollection[] + */ + private $items = array(); + + /** + * @var int + */ + private $matchStart = 0; + + public function __construct($prefix = '') + { + $this->prefix = $prefix; + } + + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return mixed[]|StaticPrefixCollection[] + */ + public function getItems() + { + return $this->items; + } + + /** + * Adds a route to a group. + * + * @param string $prefix + * @param mixed $route + */ + public function addRoute($prefix, $route) + { + $prefix = '/' === $prefix ? $prefix : rtrim($prefix, '/'); + $this->guardAgainstAddingNotAcceptedRoutes($prefix); + + if ($this->prefix === $prefix) { + // When a prefix is exactly the same as the base we move up the match start position. + // This is needed because otherwise routes that come afterwards have higher precedence + // than a possible regular expression, which goes against the input order sorting. + $this->items[] = array($prefix, $route); + $this->matchStart = count($this->items); + + return; + } + + foreach ($this->items as $i => $item) { + if ($i < $this->matchStart) { + continue; + } + + if ($item instanceof self && $item->accepts($prefix)) { + $item->addRoute($prefix, $route); + + return; + } + + $group = $this->groupWithItem($item, $prefix, $route); + + if ($group instanceof self) { + $this->items[$i] = $group; + + return; + } + } + + // No optimised case was found, in this case we simple add the route for possible + // grouping when new routes are added. + $this->items[] = array($prefix, $route); + } + + /** + * Tries to combine a route with another route or group. + * + * @param StaticPrefixCollection|array $item + * @param string $prefix + * @param mixed $route + * + * @return null|StaticPrefixCollection + */ + private function groupWithItem($item, $prefix, $route) + { + $itemPrefix = $item instanceof self ? $item->prefix : $item[0]; + $commonPrefix = $this->detectCommonPrefix($prefix, $itemPrefix); + + if (!$commonPrefix) { + return; + } + + $child = new self($commonPrefix); + + if ($item instanceof self) { + $child->items = array($item); + } else { + $child->addRoute($item[0], $item[1]); + } + + $child->addRoute($prefix, $route); + + return $child; + } + + /** + * Checks whether a prefix can be contained within the group. + * + * @param string $prefix + * + * @return bool Whether a prefix could belong in a given group + */ + private function accepts($prefix) + { + return '' === $this->prefix || 0 === strpos($prefix, $this->prefix); + } + + /** + * Detects whether there's a common prefix relative to the group prefix and returns it. + * + * @param string $prefix + * @param string $anotherPrefix + * + * @return false|string A common prefix, longer than the base/group prefix, or false when none available + */ + private function detectCommonPrefix($prefix, $anotherPrefix) + { + $baseLength = strlen($this->prefix); + $commonLength = $baseLength; + $end = min(strlen($prefix), strlen($anotherPrefix)); + + for ($i = $baseLength; $i <= $end; ++$i) { + if (substr($prefix, 0, $i) !== substr($anotherPrefix, 0, $i)) { + break; + } + + $commonLength = $i; + } + + $commonPrefix = rtrim(substr($prefix, 0, $commonLength), '/'); + + if (strlen($commonPrefix) > $baseLength) { + return $commonPrefix; + } + + return false; + } + + /** + * Optimizes the tree by inlining items from groups with less than 3 items. + */ + public function optimizeGroups() + { + $index = -1; + + while (isset($this->items[++$index])) { + $item = $this->items[$index]; + + if ($item instanceof self) { + $item->optimizeGroups(); + + // When a group contains only two items there's no reason to optimize because at minimum + // the amount of prefix check is 2. In this case inline the group. + if ($item->shouldBeInlined()) { + array_splice($this->items, $index, 1, $item->items); + + // Lower index to pass through the same index again after optimizing. + // The first item of the replacements might be a group needing optimization. + --$index; + } + } + } + } + + private function shouldBeInlined() + { + if (count($this->items) >= 3) { + return false; + } + + foreach ($this->items as $item) { + if ($item instanceof self) { + return true; + } + } + + foreach ($this->items as $item) { + if (is_array($item) && $item[0] === $this->prefix) { + return false; + } + } + + return true; + } + + /** + * Guards against adding incompatible prefixes in a group. + * + * @param string $prefix + * + * @throws \LogicException when a prefix does not belong in a group + */ + private function guardAgainstAddingNotAcceptedRoutes($prefix) + { + if (!$this->accepts($prefix)) { + $message = sprintf('Could not add route with prefix %s to collection with prefix %s', $prefix, $this->prefix); + + throw new \LogicException($message); + } + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php new file mode 100644 index 0000000..3770a9c --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Route; + +/** + * @author Fabien Potencier + */ +abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + try { + $parameters = parent::match($pathinfo); + } catch (ResourceNotFoundException $e) { + if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) { + throw $e; + } + + try { + $parameters = parent::match($pathinfo.'/'); + + return array_replace($parameters, $this->redirect($pathinfo.'/', isset($parameters['_route']) ? $parameters['_route'] : null)); + } catch (ResourceNotFoundException $e2) { + throw $e; + } + } + + return $parameters; + } + + /** + * {@inheritdoc} + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $schemes = $route->getSchemes(); + if ($schemes && !$route->hasScheme($scheme)) { + return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes))); + } + + return array(self::REQUIREMENT_MATCH, null); + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php new file mode 100644 index 0000000..7c27bc8 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +/** + * RedirectableUrlMatcherInterface knows how to redirect the user. + * + * @author Fabien Potencier + */ +interface RedirectableUrlMatcherInterface +{ + /** + * Redirects the user to another URL. + * + * @param string $path The path info to redirect to + * @param string $route The route name that matched + * @param string|null $scheme The URL scheme (null to keep the current one) + * + * @return array An array of parameters + */ + public function redirect($path, $route, $scheme = null); +} diff --git a/assets/php/vendor/symfony/routing/Matcher/RequestMatcherInterface.php b/assets/php/vendor/symfony/routing/Matcher/RequestMatcherInterface.php new file mode 100644 index 0000000..1eef778 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/RequestMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * RequestMatcherInterface is the interface that all request matcher classes must implement. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Tries to match a request with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @return array An array of parameters + * + * @throws NoConfigurationException If no routing configuration could be found + * @throws ResourceNotFoundException If no matching resource could be found + * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed + */ + public function matchRequest(Request $request); +} diff --git a/assets/php/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php b/assets/php/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php new file mode 100644 index 0000000..9085be0 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ExceptionInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * TraceableUrlMatcher helps debug path info matching by tracing the match. + * + * @author Fabien Potencier + */ +class TraceableUrlMatcher extends UrlMatcher +{ + const ROUTE_DOES_NOT_MATCH = 0; + const ROUTE_ALMOST_MATCHES = 1; + const ROUTE_MATCHES = 2; + + protected $traces; + + public function getTraces($pathinfo) + { + $this->traces = array(); + + try { + $this->match($pathinfo); + } catch (ExceptionInterface $e) { + } + + return $this->traces; + } + + public function getTracesForRequest(Request $request) + { + $this->request = $request; + $traces = $this->getTraces($request->getPathInfo()); + $this->request = null; + + return $traces; + } + + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + // does it match without any requirements? + $r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions()); + $cr = $r->compile(); + if (!preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + + continue; + } + + foreach ($route->getRequirements() as $n => $regex) { + $r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions()); + $cr = $r->compile(); + + if (in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue 2; + } + } + + continue; + } + + // check host requirement + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + + $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check condition + if ($condition = $route->getCondition()) { + if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check HTTP scheme requirement + if ($requiredSchemes = $route->getSchemes()) { + $scheme = $this->context->getScheme(); + + if (!$route->hasScheme($scheme)) { + $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $scheme, implode(', ', $requiredSchemes)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + return true; + } + } + + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + + return true; + } + } + + private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) + { + $this->traces[] = array( + 'log' => $log, + 'name' => $name, + 'level' => $level, + 'path' => null !== $route ? $route->getPath() : null, + ); + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/UrlMatcher.php b/assets/php/vendor/symfony/routing/Matcher/UrlMatcher.php new file mode 100644 index 0000000..445cfc4 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/UrlMatcher.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * UrlMatcher matches URL based on a set of routes. + * + * @author Fabien Potencier + */ +class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface +{ + const REQUIREMENT_MATCH = 0; + const REQUIREMENT_MISMATCH = 1; + const ROUTE_MATCH = 2; + + protected $context; + protected $allow = array(); + protected $routes; + protected $request; + protected $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + protected $expressionLanguageProviders = array(); + + public function __construct(RouteCollection $routes, RequestContext $context) + { + $this->routes = $routes; + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + $this->allow = array(); + + if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { + return $ret; + } + + if ('/' === $pathinfo && !$this->allow) { + throw new NoConfigurationException(); + } + + throw 0 < count($this->allow) + ? new MethodNotAllowedException(array_unique($this->allow)) + : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $this->request = $request; + + $ret = $this->match($request->getPathInfo()); + + $this->request = null; + + return $ret; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Tries to match a URL with a set of routes. + * + * @param string $pathinfo The path info to be parsed + * @param RouteCollection $routes The set of routes + * + * @return array An array of parameters + * + * @throws NoConfigurationException If no routing configuration could be found + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + // check the static prefix of the URL first. Only use the more expensive preg_match when it matches + if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { + continue; + } + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + continue; + } + + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + continue; + } + + $status = $this->handleRouteRequirements($pathinfo, $name, $route); + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $requiredMethods)) { + if (self::REQUIREMENT_MATCH === $status[0]) { + $this->allow = array_merge($this->allow, $requiredMethods); + } + + continue; + } + } + + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array())); + } + } + + /** + * Returns an array of values to use as request attributes. + * + * As this method requires the Route object, it is not available + * in matchers that do not have access to the matched Route instance + * (like the PHP and Apache matcher dumpers). + * + * @param Route $route The route we are matching against + * @param string $name The name of the route + * @param array $attributes An array of attributes from the matcher + * + * @return array An array of parameters + */ + protected function getAttributes(Route $route, $name, array $attributes) + { + $attributes['_route'] = $name; + + return $this->mergeDefaults($attributes, $route->getDefaults()); + } + + /** + * Handles specific route requirements. + * + * @param string $pathinfo The path + * @param string $name The route name + * @param Route $route The route + * + * @return array The first element represents the status, the second contains additional information + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; + + return array($status, null); + } + + /** + * Get merged default parameters. + * + * @param array $params The parameters + * @param array $defaults The defaults + * + * @return array Merged default parameters + */ + protected function mergeDefaults($params, $defaults) + { + foreach ($params as $key => $value) { + if (!is_int($key)) { + $defaults[$key] = $value; + } + } + + return $defaults; + } + + protected function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } + + /** + * @internal + */ + protected function createRequest($pathinfo) + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return null; + } + + return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), array(), array(), array( + 'SCRIPT_FILENAME' => $this->context->getBaseUrl(), + 'SCRIPT_NAME' => $this->context->getBaseUrl(), + )); + } +} diff --git a/assets/php/vendor/symfony/routing/Matcher/UrlMatcherInterface.php b/assets/php/vendor/symfony/routing/Matcher/UrlMatcherInterface.php new file mode 100644 index 0000000..2c7c313 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Matcher/UrlMatcherInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * UrlMatcherInterface is the interface that all URL matcher classes must implement. + * + * @author Fabien Potencier + */ +interface UrlMatcherInterface extends RequestContextAwareInterface +{ + /** + * Tries to match a URL path with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) + * + * @return array An array of parameters + * + * @throws NoConfigurationException If no routing configuration could be found + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + public function match($pathinfo); +} diff --git a/assets/php/vendor/symfony/routing/README.md b/assets/php/vendor/symfony/routing/README.md new file mode 100644 index 0000000..88fb1fd --- /dev/null +++ b/assets/php/vendor/symfony/routing/README.md @@ -0,0 +1,13 @@ +Routing Component +================= + +The Routing component maps an HTTP request to a set of configuration variables. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/routing/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/assets/php/vendor/symfony/routing/RequestContext.php b/assets/php/vendor/symfony/routing/RequestContext.php new file mode 100644 index 0000000..d62a776 --- /dev/null +++ b/assets/php/vendor/symfony/routing/RequestContext.php @@ -0,0 +1,336 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Holds information about the current request. + * + * This class implements a fluent interface. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RequestContext +{ + private $baseUrl; + private $pathInfo; + private $method; + private $host; + private $scheme; + private $httpPort; + private $httpsPort; + private $queryString; + private $parameters = array(); + + /** + * @param string $baseUrl The base URL + * @param string $method The HTTP method + * @param string $host The HTTP host name + * @param string $scheme The HTTP scheme + * @param int $httpPort The HTTP port + * @param int $httpsPort The HTTPS port + * @param string $path The path + * @param string $queryString The query string + */ + public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '') + { + $this->setBaseUrl($baseUrl); + $this->setMethod($method); + $this->setHost($host); + $this->setScheme($scheme); + $this->setHttpPort($httpPort); + $this->setHttpsPort($httpsPort); + $this->setPathInfo($path); + $this->setQueryString($queryString); + } + + /** + * Updates the RequestContext information based on a HttpFoundation Request. + * + * @return $this + */ + public function fromRequest(Request $request) + { + $this->setBaseUrl($request->getBaseUrl()); + $this->setPathInfo($request->getPathInfo()); + $this->setMethod($request->getMethod()); + $this->setHost($request->getHost()); + $this->setScheme($request->getScheme()); + $this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort()); + $this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort); + $this->setQueryString($request->server->get('QUERY_STRING', '')); + + return $this; + } + + /** + * Gets the base URL. + * + * @return string The base URL + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Sets the base URL. + * + * @param string $baseUrl The base URL + * + * @return $this + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + + return $this; + } + + /** + * Gets the path info. + * + * @return string The path info + */ + public function getPathInfo() + { + return $this->pathInfo; + } + + /** + * Sets the path info. + * + * @param string $pathInfo The path info + * + * @return $this + */ + public function setPathInfo($pathInfo) + { + $this->pathInfo = $pathInfo; + + return $this; + } + + /** + * Gets the HTTP method. + * + * The method is always an uppercased string. + * + * @return string The HTTP method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Sets the HTTP method. + * + * @param string $method The HTTP method + * + * @return $this + */ + public function setMethod($method) + { + $this->method = strtoupper($method); + + return $this; + } + + /** + * Gets the HTTP host. + * + * The host is always lowercased because it must be treated case-insensitive. + * + * @return string The HTTP host + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the HTTP host. + * + * @param string $host The HTTP host + * + * @return $this + */ + public function setHost($host) + { + $this->host = strtolower($host); + + return $this; + } + + /** + * Gets the HTTP scheme. + * + * @return string The HTTP scheme + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Sets the HTTP scheme. + * + * @param string $scheme The HTTP scheme + * + * @return $this + */ + public function setScheme($scheme) + { + $this->scheme = strtolower($scheme); + + return $this; + } + + /** + * Gets the HTTP port. + * + * @return int The HTTP port + */ + public function getHttpPort() + { + return $this->httpPort; + } + + /** + * Sets the HTTP port. + * + * @param int $httpPort The HTTP port + * + * @return $this + */ + public function setHttpPort($httpPort) + { + $this->httpPort = (int) $httpPort; + + return $this; + } + + /** + * Gets the HTTPS port. + * + * @return int The HTTPS port + */ + public function getHttpsPort() + { + return $this->httpsPort; + } + + /** + * Sets the HTTPS port. + * + * @param int $httpsPort The HTTPS port + * + * @return $this + */ + public function setHttpsPort($httpsPort) + { + $this->httpsPort = (int) $httpsPort; + + return $this; + } + + /** + * Gets the query string. + * + * @return string The query string without the "?" + */ + public function getQueryString() + { + return $this->queryString; + } + + /** + * Sets the query string. + * + * @param string $queryString The query string (after "?") + * + * @return $this + */ + public function setQueryString($queryString) + { + // string cast to be fault-tolerant, accepting null + $this->queryString = (string) $queryString; + + return $this; + } + + /** + * Returns the parameters. + * + * @return array The parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Sets the parameters. + * + * @param array $parameters The parameters + * + * @return $this + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * Gets a parameter value. + * + * @param string $name A parameter name + * + * @return mixed The parameter value or null if nonexistent + */ + public function getParameter($name) + { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + /** + * Checks if a parameter value is set for the given parameter. + * + * @param string $name A parameter name + * + * @return bool True if the parameter value is set, false otherwise + */ + public function hasParameter($name) + { + return array_key_exists($name, $this->parameters); + } + + /** + * Sets a parameter value. + * + * @param string $name A parameter name + * @param mixed $parameter The parameter value + * + * @return $this + */ + public function setParameter($name, $parameter) + { + $this->parameters[$name] = $parameter; + + return $this; + } +} diff --git a/assets/php/vendor/symfony/routing/RequestContextAwareInterface.php b/assets/php/vendor/symfony/routing/RequestContextAwareInterface.php new file mode 100644 index 0000000..df5b9fc --- /dev/null +++ b/assets/php/vendor/symfony/routing/RequestContextAwareInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +interface RequestContextAwareInterface +{ + /** + * Sets the request context. + */ + public function setContext(RequestContext $context); + + /** + * Gets the request context. + * + * @return RequestContext The context + */ + public function getContext(); +} diff --git a/assets/php/vendor/symfony/routing/Route.php b/assets/php/vendor/symfony/routing/Route.php new file mode 100644 index 0000000..cd50ac8 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Route.php @@ -0,0 +1,558 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * A Route describes a route and its parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class Route implements \Serializable +{ + private $path = '/'; + private $host = ''; + private $schemes = array(); + private $methods = array(); + private $defaults = array(); + private $requirements = array(); + private $options = array(); + private $condition = ''; + + /** + * @var null|CompiledRoute + */ + private $compiled; + + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * * utf8: Whether UTF-8 matching is enforced ot not + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|string[] $schemes A required URI scheme or an array of restricted schemes + * @param string|string[] $methods A required HTTP method or an array of restricted methods + * @param string $condition A condition that should evaluate to true for the route to match + */ + public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '') + { + $this->setPath($path); + $this->setDefaults($defaults); + $this->setRequirements($requirements); + $this->setOptions($options); + $this->setHost($host); + $this->setSchemes($schemes); + $this->setMethods($methods); + $this->setCondition($condition); + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'path' => $this->path, + 'host' => $this->host, + 'defaults' => $this->defaults, + 'requirements' => $this->requirements, + 'options' => $this->options, + 'schemes' => $this->schemes, + 'methods' => $this->methods, + 'condition' => $this->condition, + 'compiled' => $this->compiled, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + $this->path = $data['path']; + $this->host = $data['host']; + $this->defaults = $data['defaults']; + $this->requirements = $data['requirements']; + $this->options = $data['options']; + $this->schemes = $data['schemes']; + $this->methods = $data['methods']; + + if (isset($data['condition'])) { + $this->condition = $data['condition']; + } + if (isset($data['compiled'])) { + $this->compiled = $data['compiled']; + } + } + + /** + * Returns the pattern for the path. + * + * @return string The path pattern + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return $this + */ + public function setPath($pattern) + { + // A pattern must start with a slash and must not have multiple slashes at the beginning because the + // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. + $this->path = '/'.ltrim(trim($pattern), '/'); + $this->compiled = null; + + return $this; + } + + /** + * Returns the pattern for the host. + * + * @return string The host pattern + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the pattern for the host. + * + * This method implements a fluent interface. + * + * @param string $pattern The host pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = (string) $pattern; + $this->compiled = null; + + return $this; + } + + /** + * Returns the lowercased schemes this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @return string[] The schemes + */ + public function getSchemes() + { + return $this->schemes; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * This method implements a fluent interface. + * + * @param string|string[] $schemes The scheme or an array of schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = array_map('strtolower', (array) $schemes); + $this->compiled = null; + + return $this; + } + + /** + * Checks if a scheme requirement has been set. + * + * @param string $scheme + * + * @return bool true if the scheme requirement exists, otherwise false + */ + public function hasScheme($scheme) + { + return in_array(strtolower($scheme), $this->schemes, true); + } + + /** + * Returns the uppercased HTTP methods this route is restricted to. + * So an empty array means that any method is allowed. + * + * @return string[] The methods + */ + public function getMethods() + { + return $this->methods; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * This method implements a fluent interface. + * + * @param string|string[] $methods The method or an array of methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = array_map('strtoupper', (array) $methods); + $this->compiled = null; + + return $this; + } + + /** + * Returns the options. + * + * @return array The options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function setOptions(array $options) + { + $this->options = array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ); + + return $this->addOptions($options); + } + + /** + * Adds options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function addOptions(array $options) + { + foreach ($options as $name => $option) { + $this->options[$name] = $option; + } + $this->compiled = null; + + return $this; + } + + /** + * Sets an option value. + * + * This method implements a fluent interface. + * + * @param string $name An option name + * @param mixed $value The option value + * + * @return $this + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + $this->compiled = null; + + return $this; + } + + /** + * Get an option value. + * + * @param string $name An option name + * + * @return mixed The option value or null when not given + */ + public function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Checks if an option has been set. + * + * @param string $name An option name + * + * @return bool true if the option is set, false otherwise + */ + public function hasOption($name) + { + return array_key_exists($name, $this->options); + } + + /** + * Returns the defaults. + * + * @return array The defaults + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Sets the defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function setDefaults(array $defaults) + { + $this->defaults = array(); + + return $this->addDefaults($defaults); + } + + /** + * Adds defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function addDefaults(array $defaults) + { + foreach ($defaults as $name => $default) { + $this->defaults[$name] = $default; + } + $this->compiled = null; + + return $this; + } + + /** + * Gets a default value. + * + * @param string $name A variable name + * + * @return mixed The default value or null when not given + */ + public function getDefault($name) + { + return isset($this->defaults[$name]) ? $this->defaults[$name] : null; + } + + /** + * Checks if a default value is set for the given variable. + * + * @param string $name A variable name + * + * @return bool true if the default value is set, false otherwise + */ + public function hasDefault($name) + { + return array_key_exists($name, $this->defaults); + } + + /** + * Sets a default value. + * + * @param string $name A variable name + * @param mixed $default The default value + * + * @return $this + */ + public function setDefault($name, $default) + { + $this->defaults[$name] = $default; + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirements. + * + * @return array The requirements + */ + public function getRequirements() + { + return $this->requirements; + } + + /** + * Sets the requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function setRequirements(array $requirements) + { + $this->requirements = array(); + + return $this->addRequirements($requirements); + } + + /** + * Adds requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function addRequirements(array $requirements) + { + foreach ($requirements as $key => $regex) { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + } + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirement for the given key. + * + * @param string $key The key + * + * @return string|null The regex or null when not given + */ + public function getRequirement($key) + { + return isset($this->requirements[$key]) ? $this->requirements[$key] : null; + } + + /** + * Checks if a requirement is set for the given key. + * + * @param string $key A variable name + * + * @return bool true if a requirement is specified, false otherwise + */ + public function hasRequirement($key) + { + return array_key_exists($key, $this->requirements); + } + + /** + * Sets a requirement for the given key. + * + * @param string $key The key + * @param string $regex The regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + $this->compiled = null; + + return $this; + } + + /** + * Returns the condition. + * + * @return string The condition + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Sets the condition. + * + * This method implements a fluent interface. + * + * @param string $condition The condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = (string) $condition; + $this->compiled = null; + + return $this; + } + + /** + * Compiles the route. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + * + * @see RouteCompiler which is responsible for the compilation process + */ + public function compile() + { + if (null !== $this->compiled) { + return $this->compiled; + } + + $class = $this->getOption('compiler_class'); + + return $this->compiled = $class::compile($this); + } + + private function sanitizeRequirement($key, $regex) + { + if (!is_string($regex)) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key)); + } + + if ('' !== $regex && '^' === $regex[0]) { + $regex = (string) substr($regex, 1); // returns false for a single character + } + + if ('$' === substr($regex, -1)) { + $regex = substr($regex, 0, -1); + } + + if ('' === $regex) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key)); + } + + return $regex; + } +} diff --git a/assets/php/vendor/symfony/routing/RouteCollection.php b/assets/php/vendor/symfony/routing/RouteCollection.php new file mode 100644 index 0000000..e22cbc5 --- /dev/null +++ b/assets/php/vendor/symfony/routing/RouteCollection.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * A RouteCollection represents a set of Route instances. + * + * When adding a route at the end of the collection, an existing route + * with the same name is removed first. So there can only be one route + * with a given name. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCollection implements \IteratorAggregate, \Countable +{ + /** + * @var Route[] + */ + private $routes = array(); + + /** + * @var array + */ + private $resources = array(); + + public function __clone() + { + foreach ($this->routes as $name => $route) { + $this->routes[$name] = clone $route; + } + } + + /** + * Gets the current RouteCollection as an Iterator that includes all routes. + * + * It implements \IteratorAggregate. + * + * @see all() + * + * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes + */ + public function getIterator() + { + return new \ArrayIterator($this->routes); + } + + /** + * Gets the number of Routes in this collection. + * + * @return int The number of routes + */ + public function count() + { + return count($this->routes); + } + + /** + * Adds a route. + * + * @param string $name The route name + * @param Route $route A Route instance + */ + public function add($name, Route $route) + { + unset($this->routes[$name]); + + $this->routes[$name] = $route; + } + + /** + * Returns all routes in this collection. + * + * @return Route[] An array of routes + */ + public function all() + { + return $this->routes; + } + + /** + * Gets a route by name. + * + * @param string $name The route name + * + * @return Route|null A Route instance or null when not found + */ + public function get($name) + { + return isset($this->routes[$name]) ? $this->routes[$name] : null; + } + + /** + * Removes a route or an array of routes by name from the collection. + * + * @param string|string[] $name The route name or an array of route names + */ + public function remove($name) + { + foreach ((array) $name as $n) { + unset($this->routes[$n]); + } + } + + /** + * Adds a route collection at the end of the current set by appending all + * routes of the added collection. + */ + public function addCollection(self $collection) + { + // we need to remove all routes with the same names first because just replacing them + // would not place the new route at the end of the merged array + foreach ($collection->all() as $name => $route) { + unset($this->routes[$name]); + $this->routes[$name] = $route; + } + + foreach ($collection->getResources() as $resource) { + $this->addResource($resource); + } + } + + /** + * Adds a prefix to the path of all child routes. + * + * @param string $prefix An optional prefix to add before each pattern of the route collection + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function addPrefix($prefix, array $defaults = array(), array $requirements = array()) + { + $prefix = trim(trim($prefix), '/'); + + if ('' === $prefix) { + return; + } + + foreach ($this->routes as $route) { + $route->setPath('/'.$prefix.$route->getPath()); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets the host pattern on all routes. + * + * @param string $pattern The pattern + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function setHost($pattern, array $defaults = array(), array $requirements = array()) + { + foreach ($this->routes as $route) { + $route->setHost($pattern); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets a condition on all routes. + * + * Existing conditions will be overridden. + * + * @param string $condition The condition + */ + public function setCondition($condition) + { + foreach ($this->routes as $route) { + $route->setCondition($condition); + } + } + + /** + * Adds defaults to all routes. + * + * An existing default value under the same name in a route will be overridden. + * + * @param array $defaults An array of default values + */ + public function addDefaults(array $defaults) + { + if ($defaults) { + foreach ($this->routes as $route) { + $route->addDefaults($defaults); + } + } + } + + /** + * Adds requirements to all routes. + * + * An existing requirement under the same name in a route will be overridden. + * + * @param array $requirements An array of requirements + */ + public function addRequirements(array $requirements) + { + if ($requirements) { + foreach ($this->routes as $route) { + $route->addRequirements($requirements); + } + } + } + + /** + * Adds options to all routes. + * + * An existing option value under the same name in a route will be overridden. + * + * @param array $options An array of options + */ + public function addOptions(array $options) + { + if ($options) { + foreach ($this->routes as $route) { + $route->addOptions($options); + } + } + } + + /** + * Sets the schemes (e.g. 'https') all child routes are restricted to. + * + * @param string|string[] $schemes The scheme or an array of schemes + */ + public function setSchemes($schemes) + { + foreach ($this->routes as $route) { + $route->setSchemes($schemes); + } + } + + /** + * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to. + * + * @param string|string[] $methods The method or an array of methods + */ + public function setMethods($methods) + { + foreach ($this->routes as $route) { + $route->setMethods($methods); + } + } + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + */ + public function getResources() + { + return array_values($this->resources); + } + + /** + * Adds a resource for this collection. If the resource already exists + * it is not added. + */ + public function addResource(ResourceInterface $resource) + { + $key = (string) $resource; + + if (!isset($this->resources[$key])) { + $this->resources[$key] = $resource; + } + } +} diff --git a/assets/php/vendor/symfony/routing/RouteCollectionBuilder.php b/assets/php/vendor/symfony/routing/RouteCollectionBuilder.php new file mode 100644 index 0000000..e8a9a16 --- /dev/null +++ b/assets/php/vendor/symfony/routing/RouteCollectionBuilder.php @@ -0,0 +1,380 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Helps add and import routes into a RouteCollection. + * + * @author Ryan Weaver + */ +class RouteCollectionBuilder +{ + /** + * @var Route[]|RouteCollectionBuilder[] + */ + private $routes = array(); + + private $loader; + private $defaults = array(); + private $prefix; + private $host; + private $condition; + private $requirements = array(); + private $options = array(); + private $schemes; + private $methods; + private $resources = array(); + + public function __construct(LoaderInterface $loader = null) + { + $this->loader = $loader; + } + + /** + * Import an external routing resource and returns the RouteCollectionBuilder. + * + * $routes->import('blog.yml', '/blog'); + * + * @param mixed $resource + * @param string|null $prefix + * @param string $type + * + * @return self + * + * @throws FileLoaderLoadException + */ + public function import($resource, $prefix = '/', $type = null) + { + /** @var RouteCollection[] $collection */ + $collections = $this->load($resource, $type); + + // create a builder from the RouteCollection + $builder = $this->createBuilder(); + + foreach ($collections as $collection) { + if (null === $collection) { + continue; + } + + foreach ($collection->all() as $name => $route) { + $builder->addRoute($route, $name); + } + + foreach ($collection->getResources() as $resource) { + $builder->addResource($resource); + } + } + + // mount into this builder + $this->mount($prefix, $builder); + + return $builder; + } + + /** + * Adds a route and returns it for future modification. + * + * @param string $path The route path + * @param string $controller The route's controller + * @param string|null $name The name to give this route + * + * @return Route + */ + public function add($path, $controller, $name = null) + { + $route = new Route($path); + $route->setDefault('_controller', $controller); + $this->addRoute($route, $name); + + return $route; + } + + /** + * Returns a RouteCollectionBuilder that can be configured and then added with mount(). + * + * @return self + */ + public function createBuilder() + { + return new self($this->loader); + } + + /** + * Add a RouteCollectionBuilder. + * + * @param string $prefix + * @param RouteCollectionBuilder $builder + */ + public function mount($prefix, RouteCollectionBuilder $builder) + { + $builder->prefix = trim(trim($prefix), '/'); + $this->routes[] = $builder; + } + + /** + * Adds a Route object to the builder. + * + * @param Route $route + * @param string|null $name + * + * @return $this + */ + public function addRoute(Route $route, $name = null) + { + if (null === $name) { + // used as a flag to know which routes will need a name later + $name = '_unnamed_route_'.spl_object_hash($route); + } + + $this->routes[$name] = $route; + + return $this; + } + + /** + * Sets the host on all embedded routes (unless already set). + * + * @param string $pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = $pattern; + + return $this; + } + + /** + * Sets a condition on all embedded routes (unless already set). + * + * @param string $condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = $condition; + + return $this; + } + + /** + * Sets a default value that will be added to all embedded routes (unless that + * default value is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setDefault($key, $value) + { + $this->defaults[$key] = $value; + + return $this; + } + + /** + * Sets a requirement that will be added to all embedded routes (unless that + * requirement is already set). + * + * @param string $key + * @param mixed $regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $regex; + + return $this; + } + + /** + * Sets an option that will be added to all embedded routes (unless that + * option is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setOption($key, $value) + { + $this->options[$key] = $value; + + return $this; + } + + /** + * Sets the schemes on all embedded routes (unless already set). + * + * @param array|string $schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = $schemes; + + return $this; + } + + /** + * Sets the methods on all embedded routes (unless already set). + * + * @param array|string $methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = $methods; + + return $this; + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource + * + * @return $this + */ + private function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + + return $this; + } + + /** + * Creates the final RouteCollection and returns it. + * + * @return RouteCollection + */ + public function build() + { + $routeCollection = new RouteCollection(); + + foreach ($this->routes as $name => $route) { + if ($route instanceof Route) { + $route->setDefaults(array_merge($this->defaults, $route->getDefaults())); + $route->setOptions(array_merge($this->options, $route->getOptions())); + + foreach ($this->requirements as $key => $val) { + if (!$route->hasRequirement($key)) { + $route->setRequirement($key, $val); + } + } + + if (null !== $this->prefix) { + $route->setPath('/'.$this->prefix.$route->getPath()); + } + + if (!$route->getHost()) { + $route->setHost($this->host); + } + + if (!$route->getCondition()) { + $route->setCondition($this->condition); + } + + if (!$route->getSchemes()) { + $route->setSchemes($this->schemes); + } + + if (!$route->getMethods()) { + $route->setMethods($this->methods); + } + + // auto-generate the route name if it's been marked + if ('_unnamed_route_' === substr($name, 0, 15)) { + $name = $this->generateRouteName($route); + } + + $routeCollection->add($name, $route); + } else { + /* @var self $route */ + $subCollection = $route->build(); + $subCollection->addPrefix($this->prefix); + + $routeCollection->addCollection($subCollection); + } + } + + foreach ($this->resources as $resource) { + $routeCollection->addResource($resource); + } + + return $routeCollection; + } + + /** + * Generates a route name based on details of this route. + * + * @return string + */ + private function generateRouteName(Route $route) + { + $methods = implode('_', $route->getMethods()).'_'; + + $routeName = $methods.$route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } + + /** + * Finds a loader able to load an imported resource and loads it. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return RouteCollection[] + * + * @throws FileLoaderLoadException If no loader is found + */ + private function load($resource, $type = null) + { + if (null === $this->loader) { + throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); + } + + if ($this->loader->supports($resource, $type)) { + $collections = $this->loader->load($resource, $type); + + return is_array($collections) ? $collections : array($collections); + } + + if (null === $resolver = $this->loader->getResolver()) { + throw new FileLoaderLoadException($resource, null, null, null, $type); + } + + if (false === $loader = $resolver->resolve($resource, $type)) { + throw new FileLoaderLoadException($resource, null, null, null, $type); + } + + $collections = $loader->load($resource, $type); + + return is_array($collections) ? $collections : array($collections); + } +} diff --git a/assets/php/vendor/symfony/routing/RouteCompiler.php b/assets/php/vendor/symfony/routing/RouteCompiler.php new file mode 100644 index 0000000..dc4e4f8 --- /dev/null +++ b/assets/php/vendor/symfony/routing/RouteCompiler.php @@ -0,0 +1,316 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompiler compiles Route instances to CompiledRoute instances. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCompiler implements RouteCompilerInterface +{ + const REGEX_DELIMITER = '#'; + + /** + * This string defines the characters that are automatically considered separators in front of + * optional placeholders (with default and no static text following). Such a single separator + * can be left out together with the optional placeholder from matching and generating URLs. + */ + const SEPARATORS = '/,;.:-_~+*=@|'; + + /** + * The maximum supported length of a PCRE subpattern name + * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16. + * + * @internal + */ + const VARIABLE_MAXIMUM_LENGTH = 32; + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException if a path variable is named _fragment + * @throws \LogicException if a variable is referenced more than once + * @throws \DomainException if a variable name starts with a digit or if it is too long to be successfully used as + * a PCRE subpattern + */ + public static function compile(Route $route) + { + $hostVariables = array(); + $variables = array(); + $hostRegex = null; + $hostTokens = array(); + + if ('' !== $host = $route->getHost()) { + $result = self::compilePattern($route, $host, true); + + $hostVariables = $result['variables']; + $variables = $hostVariables; + + $hostTokens = $result['tokens']; + $hostRegex = $result['regex']; + } + + $path = $route->getPath(); + + $result = self::compilePattern($route, $path, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + + foreach ($pathVariables as $pathParam) { + if ('_fragment' === $pathParam) { + throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath())); + } + } + + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + $pathVariables, + $hostRegex, + $hostTokens, + $hostVariables, + array_unique($variables) + ); + } + + private static function compilePattern(Route $route, $pattern, $isHost) + { + $tokens = array(); + $variables = array(); + $matches = array(); + $pos = 0; + $defaultSeparator = $isHost ? '.' : '/'; + $useUtf8 = preg_match('//u', $pattern); + $needsUtf8 = $route->getOption('utf8'); + + if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) { + $needsUtf8 = true; + @trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), E_USER_DEPRECATED); + } + if (!$useUtf8 && $needsUtf8) { + throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern)); + } + + // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable + // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. + preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + foreach ($matches as $match) { + $varName = substr($match[0][0], 1, -1); + // get all static text preceding the current variable + $precedingText = substr($pattern, $pos, $match[0][1] - $pos); + $pos = $match[0][1] + strlen($match[0][0]); + + if (!strlen($precedingText)) { + $precedingChar = ''; + } elseif ($useUtf8) { + preg_match('/.$/u', $precedingText, $precedingChar); + $precedingChar = $precedingChar[0]; + } else { + $precedingChar = substr($precedingText, -1); + } + $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + + // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the + // variable would not be usable as a Controller action argument. + if (preg_match('/^\d/', $varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern)); + } + if (in_array($varName, $variables)) { + throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); + } + + if (strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) { + throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern)); + } + + if ($isSeparator && $precedingText !== $precedingChar) { + $tokens[] = array('text', substr($precedingText, 0, -strlen($precedingChar))); + } elseif (!$isSeparator && strlen($precedingText) > 0) { + $tokens[] = array('text', $precedingText); + } + + $regexp = $route->getRequirement($varName); + if (null === $regexp) { + $followingPattern = (string) substr($pattern, $pos); + // Find the next static character after the variable that functions as a separator. By default, this separator and '/' + // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all + // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are + // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html')) + // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. + // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally + // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. + $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator, self::REGEX_DELIMITER), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + ); + if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { + // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive + // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. + // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow + // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is + // directly adjacent, e.g. '/{x}{y}'. + $regexp .= '+'; + } + } else { + if (!preg_match('//u', $regexp)) { + $useUtf8 = false; + } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?= 0; --$i) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } + } + } + + // compute the matching regexp + $regexp = ''; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { + $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + } + $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'sD'.($isHost ? 'i' : ''); + + // enable Utf8 matching if really required + if ($needsUtf8) { + $regexp .= 'u'; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { + if ('variable' === $tokens[$i][0]) { + $tokens[$i][] = true; + } + } + } + + return array( + 'staticPrefix' => self::determineStaticPrefix($route, $tokens), + 'regex' => $regexp, + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ); + } + + /** + * Determines the longest static prefix possible for a route. + * + * @return string The leading static part of a route's path + */ + private static function determineStaticPrefix(Route $route, array $tokens) + { + if ('text' !== $tokens[0][0]) { + return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1]; + } + + $prefix = $tokens[0][1]; + + if (isset($tokens[1][1]) && '/' !== $tokens[1][1] && false === $route->hasDefault($tokens[1][3])) { + $prefix .= $tokens[1][1]; + } + + return $prefix; + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator. + * + * @param string $pattern The route pattern + * @param bool $useUtf8 Whether the character is encoded in UTF-8 or not + * + * @return string The next static character that functions as separator (or empty string when none available) + */ + private static function findNextSeparator($pattern, $useUtf8) + { + if ('' == $pattern) { + // return empty string if pattern is empty or false (false which can be returned by substr) + return ''; + } + // first remove all placeholders from the pattern so we can find the next real static character + if ('' === $pattern = preg_replace('#\{\w+\}#', '', $pattern)) { + return ''; + } + if ($useUtf8) { + preg_match('/^./u', $pattern, $pattern); + } + + return false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + } + + /** + * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * + * @param array $tokens The route tokens + * @param int $index The index of the current token + * @param int $firstOptional The index of the first optional token + * + * @return string The regexp pattern for a single token + */ + private static function computeRegexp(array $tokens, $index, $firstOptional) + { + $token = $tokens[$index]; + if ('text' === $token[0]) { + // Text tokens + return preg_quote($token[1], self::REGEX_DELIMITER); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + } else { + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + $nbTokens = count($tokens); + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); + } + } + + return $regexp; + } + } + } +} diff --git a/assets/php/vendor/symfony/routing/RouteCompilerInterface.php b/assets/php/vendor/symfony/routing/RouteCompilerInterface.php new file mode 100644 index 0000000..ddfa7ca --- /dev/null +++ b/assets/php/vendor/symfony/routing/RouteCompilerInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompilerInterface is the interface that all RouteCompiler classes must implement. + * + * @author Fabien Potencier + */ +interface RouteCompilerInterface +{ + /** + * Compiles the current route instance. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + */ + public static function compile(Route $route); +} diff --git a/assets/php/vendor/symfony/routing/Router.php b/assets/php/vendor/symfony/routing/Router.php new file mode 100644 index 0000000..ed56332 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Router.php @@ -0,0 +1,388 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheFactory; +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * The Router class is an example of the integration of all pieces of the + * routing system for easier use. + * + * @author Fabien Potencier + */ +class Router implements RouterInterface, RequestMatcherInterface +{ + /** + * @var UrlMatcherInterface|null + */ + protected $matcher; + + /** + * @var UrlGeneratorInterface|null + */ + protected $generator; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var LoaderInterface + */ + protected $loader; + + /** + * @var RouteCollection|null + */ + protected $collection; + + /** + * @var mixed + */ + protected $resource; + + /** + * @var array + */ + protected $options = array(); + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * @var ConfigCacheFactoryInterface|null + */ + private $configCacheFactory; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * @param LoaderInterface $loader A LoaderInterface instance + * @param mixed $resource The main resource to load + * @param array $options An array of options + * @param RequestContext $context The context + * @param LoggerInterface $logger A logger instance + */ + public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null) + { + $this->loader = $loader; + $this->resource = $resource; + $this->logger = $logger; + $this->context = $context ?: new RequestContext(); + $this->setOptions($options); + } + + /** + * Sets options. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * generator_class: The name of a UrlGeneratorInterface implementation + * * generator_base_class: The base class for the dumped generator class + * * generator_cache_class: The class name for the dumped generator class + * * generator_dumper_class: The name of a GeneratorDumperInterface implementation + * * matcher_class: The name of a UrlMatcherInterface implementation + * * matcher_base_class: The base class for the dumped matcher class + * * matcher_dumper_class: The class name for the dumped matcher class + * * matcher_cache_class: The name of a MatcherDumperInterface implementation + * * resource_type: Type hint for the main resource (optional) + * * strict_requirements: Configure strict requirement checking for generators + * implementing ConfigurableRequirementsInterface (default is true) + * + * @param array $options An array of options + * + * @throws \InvalidArgumentException When unsupported option is provided + */ + public function setOptions(array $options) + { + $this->options = array( + 'cache_dir' => null, + 'debug' => false, + 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', + 'generator_cache_class' => 'ProjectUrlGenerator', + 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', + 'matcher_cache_class' => 'ProjectUrlMatcher', + 'resource_type' => null, + 'strict_requirements' => true, + ); + + // check option names and live merge, if errors are encountered Exception will be thrown + $invalid = array(); + foreach ($options as $key => $value) { + if (array_key_exists($key, $this->options)) { + $this->options[$key] = $value; + } else { + $invalid[] = $key; + } + } + + if ($invalid) { + throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid))); + } + } + + /** + * Sets an option. + * + * @param string $key The key + * @param mixed $value The value + * + * @throws \InvalidArgumentException + */ + public function setOption($key, $value) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + $this->options[$key] = $value; + } + + /** + * Gets an option value. + * + * @param string $key The key + * + * @return mixed The value + * + * @throws \InvalidArgumentException + */ + public function getOption($key) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + return $this->options[$key]; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() + { + if (null === $this->collection) { + $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); + } + + return $this->collection; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + + if (null !== $this->matcher) { + $this->getMatcher()->setContext($context); + } + if (null !== $this->generator) { + $this->getGenerator()->setContext($context); + } + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * Sets the ConfigCache factory to use. + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->getMatcher()->match($pathinfo); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $matcher = $this->getMatcher(); + if (!$matcher instanceof RequestMatcherInterface) { + // fallback to the default UrlMatcherInterface + return $matcher->match($request->getPathInfo()); + } + + return $matcher->matchRequest($request); + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return UrlMatcherInterface A UrlMatcherInterface instance + */ + public function getMatcher() + { + if (null !== $this->matcher) { + return $this->matcher; + } + + if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { + $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context); + if (method_exists($this->matcher, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $this->matcher->addExpressionLanguageProvider($provider); + } + } + + return $this->matcher; + } + + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getMatcherDumperInstance(); + if (method_exists($dumper, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $dumper->addExpressionLanguageProvider($provider); + } + } + + $options = array( + 'class' => $this->options['matcher_cache_class'], + 'base_class' => $this->options['matcher_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + ); + + if (!class_exists($this->options['matcher_cache_class'], false)) { + require_once $cache->getPath(); + } + + return $this->matcher = new $this->options['matcher_cache_class']($this->context); + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return UrlGeneratorInterface A UrlGeneratorInterface instance + */ + public function getGenerator() + { + if (null !== $this->generator) { + return $this->generator; + } + + if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { + $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger); + } else { + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getGeneratorDumperInstance(); + + $options = array( + 'class' => $this->options['generator_cache_class'], + 'base_class' => $this->options['generator_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + ); + + if (!class_exists($this->options['generator_cache_class'], false)) { + require_once $cache->getPath(); + } + + $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger); + } + + if ($this->generator instanceof ConfigurableRequirementsInterface) { + $this->generator->setStrictRequirements($this->options['strict_requirements']); + } + + return $this->generator; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * @return GeneratorDumperInterface + */ + protected function getGeneratorDumperInstance() + { + return new $this->options['generator_dumper_class']($this->getRouteCollection()); + } + + /** + * @return MatcherDumperInterface + */ + protected function getMatcherDumperInstance() + { + return new $this->options['matcher_dumper_class']($this->getRouteCollection()); + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + * + * @return ConfigCacheFactoryInterface $configCacheFactory + */ + private function getConfigCacheFactory() + { + if (null === $this->configCacheFactory) { + $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']); + } + + return $this->configCacheFactory; + } +} diff --git a/assets/php/vendor/symfony/routing/RouterInterface.php b/assets/php/vendor/symfony/routing/RouterInterface.php new file mode 100644 index 0000000..a10ae34 --- /dev/null +++ b/assets/php/vendor/symfony/routing/RouterInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * RouterInterface is the interface that all Router classes must implement. + * + * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. + * + * @author Fabien Potencier + */ +interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface +{ + /** + * Gets the RouteCollection instance associated with this Router. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRouteCollection(); +} diff --git a/assets/php/vendor/symfony/routing/Tests/Annotation/RouteTest.php b/assets/php/vendor/symfony/routing/Tests/Annotation/RouteTest.php new file mode 100644 index 0000000..9af22f2 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Annotation/RouteTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Annotation; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Annotation\Route; + +class RouteTest extends TestCase +{ + /** + * @expectedException \BadMethodCallException + */ + public function testInvalidRouteParameter() + { + $route = new Route(array('foo' => 'bar')); + } + + /** + * @dataProvider getValidParameters + */ + public function testRouteParameters($parameter, $value, $getter) + { + $route = new Route(array($parameter => $value)); + $this->assertEquals($route->$getter(), $value); + } + + public function getValidParameters() + { + return array( + array('value', '/Blog', 'getPath'), + array('requirements', array('locale' => 'en'), 'getRequirements'), + array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'), + array('name', 'blog_index', 'getName'), + array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'), + array('schemes', array('https'), 'getSchemes'), + array('methods', array('GET', 'POST'), 'getMethods'), + array('host', '{locale}.example.com', 'getHost'), + array('condition', 'context.getMethod() == "GET"', 'getCondition'), + ); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/CompiledRouteTest.php b/assets/php/vendor/symfony/routing/Tests/CompiledRouteTest.php new file mode 100644 index 0000000..5bec7bb --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/CompiledRouteTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\CompiledRoute; + +class CompiledRouteTest extends TestCase +{ + public function testAccessors() + { + $compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array(), null, array(), array(), array('variables')); + $this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its second argument'); + $this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its third argument'); + $this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its fourth argument'); + $this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its ninth argument'); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php b/assets/php/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php new file mode 100644 index 0000000..97a34c9 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; + +class RoutingResolverPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('routing.resolver', LoaderResolver::class); + $container->register('loader1')->addTag('routing.loader'); + $container->register('loader2')->addTag('routing.loader'); + + (new RoutingResolverPass())->process($container); + + $this->assertEquals( + array(array('addLoader', array(new Reference('loader1'))), array('addLoader', array(new Reference('loader2')))), + $container->getDefinition('routing.resolver')->getMethodCalls() + ); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php new file mode 100644 index 0000000..56bcab2 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +abstract class AbstractClass +{ +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php new file mode 100644 index 0000000..a388277 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class BarClass +{ + public function routeAction($arg1, $arg2 = 'defaultValue2', $arg3 = 'defaultValue3') + { + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php new file mode 100644 index 0000000..471968b --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class BazClass +{ + public function __invoke() + { + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php new file mode 100644 index 0000000..320dc35 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class FooClass +{ +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php new file mode 100644 index 0000000..ee8f4b0 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\CompiledRoute; + +class CustomCompiledRoute extends CompiledRoute +{ +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php new file mode 100644 index 0000000..c2e2afd --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCompiler; + +class CustomRouteCompiler extends RouteCompiler +{ + /** + * {@inheritdoc} + */ + public static function compile(Route $route) + { + return new CustomCompiledRoute('', '', array(), array()); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php new file mode 100644 index 0000000..9fd5754 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader with schema validation turned off. + */ +class CustomXmlFileLoader extends XmlFileLoader +{ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, function () { return true; }); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php new file mode 100644 index 0000000..de87895 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\OtherAnnotatedClasses; + +trait AnonymousClassInTrait +{ + public function test() + { + return new class() { + public function foo() + { + } + }; + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php new file mode 100644 index 0000000..8900d34 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php @@ -0,0 +1,3 @@ +class NoStartTagClass +{ +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php new file mode 100644 index 0000000..729c9b4 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\OtherAnnotatedClasses; + +class VariadicClass +{ + public function routeAction(...$params) + { + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php new file mode 100644 index 0000000..15937bc --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; + +/** + * @author Fabien Potencier + */ +class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect($path, $route, $scheme = null) + { + return array( + '_controller' => 'Some controller reference...', + 'path' => $path, + 'scheme' => $scheme, + ); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/annotated.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/annotated.php new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/bad_format.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/bad_format.yml new file mode 100644 index 0000000..8ba50e2 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/bad_format.yml @@ -0,0 +1,3 @@ +blog_show: + path: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/bar.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/bar.xml new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml new file mode 100644 index 0000000..bbe727d --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml @@ -0,0 +1,10 @@ + + + + + FrameworkBundle:Template:template + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml new file mode 100644 index 0000000..4240b74 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml @@ -0,0 +1,4 @@ +_static: + resource: routing.yml + defaults: + _controller: FrameworkBundle:Template:template diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml new file mode 100644 index 0000000..378b9ca --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml new file mode 100644 index 0000000..1a71c62 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml @@ -0,0 +1,3 @@ +_static: + resource: routing.yml + controller: FrameworkBundle:Template:template diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml new file mode 100644 index 0000000..e3c755a --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml @@ -0,0 +1,10 @@ + + + + + AppBundle:Blog:index + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml new file mode 100644 index 0000000..db1ab3c --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml @@ -0,0 +1,5 @@ +_static: + resource: routing.yml + controller: FrameworkBundle:Template:template + defaults: + _controller: AppBundle:Homepage:show diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml new file mode 100644 index 0000000..f47c57b --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml @@ -0,0 +1,10 @@ + + + + + AppBundle:Blog:index + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml new file mode 100644 index 0000000..00a2c0e --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml @@ -0,0 +1,5 @@ +app_blog: + path: /blog + controller: AppBundle:Homepage:show + defaults: + _controller: AppBundle:Blog:index diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml new file mode 100644 index 0000000..6420138 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml @@ -0,0 +1,14 @@ + + + + + + + AppBundle:Blog:list + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml new file mode 100644 index 0000000..cb71ec3 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml @@ -0,0 +1,11 @@ +app_homepage: + path: / + controller: AppBundle:Homepage:show + +app_blog: + path: /blog + defaults: + _controller: AppBundle:Blog:list + +app_logout: + path: /logout diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml new file mode 100644 index 0000000..d078836 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml @@ -0,0 +1,2 @@ +route1: + path: /route/1 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml new file mode 100644 index 0000000..938fb24 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml @@ -0,0 +1,2 @@ +route2: + path: /route/2 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml new file mode 100644 index 0000000..088cfb4 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml @@ -0,0 +1,2 @@ +route3: + path: /route/3 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml new file mode 100644 index 0000000..af829e5 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml @@ -0,0 +1,3 @@ +_directory: + resource: "../directory" + type: directory diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php new file mode 100644 index 0000000..9e9b910 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php @@ -0,0 +1,37 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php new file mode 100644 index 0000000..23a93c1 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php @@ -0,0 +1,318 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + if (0 === strpos($pathinfo, '/foo')) { + // foo + if (preg_match('#^/foo/(?Pbaz|symfony)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + // foofoo + if ('/foofoo' === $pathinfo) { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + } + + elseif (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + if (!in_array($canonicalMethod, array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_bar; + } + + return $ret; + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_barhead; + } + + return $ret; + } + not_barhead: + + } + + elseif (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ('/test/baz' === $pathinfo) { + return array('_route' => 'baz'); + } + + // baz2 + if ('/test/baz.html' === $pathinfo) { + return array('_route' => 'baz2'); + } + + // baz3 + if ('/test/baz3/' === $pathinfo) { + return array('_route' => 'baz3'); + } + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + } + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_baz5; + } + + return $ret; + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + if (!in_array($requestMethod, array('PUT'))) { + $allow = array_merge($allow, array('PUT')); + goto not_bazbaz6; + } + + return $ret; + } + not_bazbaz6: + + } + + // quoter + if (preg_match('#^/(?P[\']+)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ('/spa ce' === $pathinfo) { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + elseif (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // hey + if ('/multi/hey/' === $pathinfo) { + return array('_route' => 'hey'); + } + + // overridden2 + if ('/multi/new' === $pathinfo) { + return array('_route' => 'overridden2'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ('/ababa' === $pathinfo) { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $context->getHost(); + + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { + // route1 + if ('/route1' === $pathinfo) { + return array('_route' => 'route1'); + } + + // route2 + if ('/c2/route2' === $pathinfo) { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) { + // route3 + if ('/c2/route3' === $pathinfo) { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { + // route4 + if ('/route4' === $pathinfo) { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { + // route5 + if ('/route5' === $pathinfo) { + return array('_route' => 'route5'); + } + + } + + // route6 + if ('/route6' === $pathinfo) { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ('/route11' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ('/route12' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ('/route17' === $pathinfo) { + return array('_route' => 'route17'); + } + + // a + if ('/a/a...' === $pathinfo) { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php new file mode 100644 index 0000000..e430adb --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php @@ -0,0 +1,380 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + if (0 === strpos($pathinfo, '/foo')) { + // foo + if (preg_match('#^/foo/(?Pbaz|symfony)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + // foofoo + if ('/foofoo' === $pathinfo) { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + } + + elseif (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + if (!in_array($canonicalMethod, array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_bar; + } + + return $ret; + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_barhead; + } + + return $ret; + } + not_barhead: + + } + + elseif (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ('/test/baz' === $pathinfo) { + return array('_route' => 'baz'); + } + + // baz2 + if ('/test/baz.html' === $pathinfo) { + return array('_route' => 'baz2'); + } + + // baz3 + if ('/test/baz3' === $trimmedPathinfo) { + $ret = array('_route' => 'baz3'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_baz3; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz3')); + } + + return $ret; + } + not_baz3: + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/?$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_baz4; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz4')); + } + + return $ret; + } + not_baz4: + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_baz5; + } + + return $ret; + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + if (!in_array($requestMethod, array('PUT'))) { + $allow = array_merge($allow, array('PUT')); + goto not_bazbaz6; + } + + return $ret; + } + not_bazbaz6: + + } + + // quoter + if (preg_match('#^/(?P[\']+)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ('/spa ce' === $pathinfo) { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + elseif (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // hey + if ('/multi/hey' === $trimmedPathinfo) { + $ret = array('_route' => 'hey'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_hey; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'hey')); + } + + return $ret; + } + not_hey: + + // overridden2 + if ('/multi/new' === $pathinfo) { + return array('_route' => 'overridden2'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ('/ababa' === $pathinfo) { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $context->getHost(); + + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { + // route1 + if ('/route1' === $pathinfo) { + return array('_route' => 'route1'); + } + + // route2 + if ('/c2/route2' === $pathinfo) { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) { + // route3 + if ('/c2/route3' === $pathinfo) { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { + // route4 + if ('/route4' === $pathinfo) { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { + // route5 + if ('/route5' === $pathinfo) { + return array('_route' => 'route5'); + } + + } + + // route6 + if ('/route6' === $pathinfo) { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ('/route11' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ('/route12' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ('/route17' === $pathinfo) { + return array('_route' => 'route17'); + } + + // a + if ('/a/a...' === $pathinfo) { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + // secure + if ('/secure' === $pathinfo) { + $ret = array('_route' => 'secure'); + $requiredSchemes = array ( 'https' => 0,); + if (!isset($requiredSchemes[$context->getScheme()])) { + if ('GET' !== $canonicalMethod) { + goto not_secure; + } + + return array_replace($ret, $this->redirect($rawPathinfo, 'secure', key($requiredSchemes))); + } + + return $ret; + } + not_secure: + + // nonsecure + if ('/nonsecure' === $pathinfo) { + $ret = array('_route' => 'nonsecure'); + $requiredSchemes = array ( 'http' => 0,); + if (!isset($requiredSchemes[$context->getScheme()])) { + if ('GET' !== $canonicalMethod) { + goto not_nonsecure; + } + + return array_replace($ret, $this->redirect($rawPathinfo, 'nonsecure', key($requiredSchemes))); + } + + return $ret; + } + not_nonsecure: + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php new file mode 100644 index 0000000..67c4667 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php @@ -0,0 +1,55 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + if (0 === strpos($pathinfo, '/rootprefix')) { + // static + if ('/rootprefix/test' === $pathinfo) { + return array('_route' => 'static'); + } + + // dynamic + if (preg_match('#^/rootprefix/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ()); + } + + } + + // with-condition + if ('/with-condition' === $pathinfo && ($context->getMethod() == "GET")) { + return array('_route' => 'with-condition'); + } + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php new file mode 100644 index 0000000..ed07194 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php @@ -0,0 +1,112 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + // just_head + if ('/just_head' === $pathinfo) { + $ret = array('_route' => 'just_head'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_just_head; + } + + return $ret; + } + not_just_head: + + // head_and_get + if ('/head_and_get' === $pathinfo) { + $ret = array('_route' => 'head_and_get'); + if (!in_array($canonicalMethod, array('HEAD', 'GET'))) { + $allow = array_merge($allow, array('HEAD', 'GET')); + goto not_head_and_get; + } + + return $ret; + } + not_head_and_get: + + // get_and_head + if ('/get_and_head' === $pathinfo) { + $ret = array('_route' => 'get_and_head'); + if (!in_array($canonicalMethod, array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_get_and_head; + } + + return $ret; + } + not_get_and_head: + + // post_and_head + if ('/post_and_head' === $pathinfo) { + $ret = array('_route' => 'post_and_head'); + if (!in_array($requestMethod, array('POST', 'HEAD'))) { + $allow = array_merge($allow, array('POST', 'HEAD')); + goto not_post_and_head; + } + + return $ret; + } + not_post_and_head: + + if (0 === strpos($pathinfo, '/put_and_post')) { + // put_and_post + if ('/put_and_post' === $pathinfo) { + $ret = array('_route' => 'put_and_post'); + if (!in_array($requestMethod, array('PUT', 'POST'))) { + $allow = array_merge($allow, array('PUT', 'POST')); + goto not_put_and_post; + } + + return $ret; + } + not_put_and_post: + + // put_and_get_and_head + if ('/put_and_post' === $pathinfo) { + $ret = array('_route' => 'put_and_get_and_head'); + if (!in_array($canonicalMethod, array('PUT', 'GET', 'HEAD'))) { + $allow = array_merge($allow, array('PUT', 'GET', 'HEAD')); + goto not_put_and_get_and_head; + } + + return $ret; + } + not_put_and_get_and_head: + + } + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php new file mode 100644 index 0000000..2b22513 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php @@ -0,0 +1,209 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + if (0 === strpos($pathinfo, '/a')) { + // a_first + if ('/a/11' === $pathinfo) { + return array('_route' => 'a_first'); + } + + // a_second + if ('/a/22' === $pathinfo) { + return array('_route' => 'a_second'); + } + + // a_third + if ('/a/333' === $pathinfo) { + return array('_route' => 'a_third'); + } + + } + + // a_wildcard + if (preg_match('#^/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'a_wildcard')), array ()); + } + + if (0 === strpos($pathinfo, '/a')) { + // a_fourth + if ('/a/44' === $trimmedPathinfo) { + $ret = array('_route' => 'a_fourth'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_a_fourth; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fourth')); + } + + return $ret; + } + not_a_fourth: + + // a_fifth + if ('/a/55' === $trimmedPathinfo) { + $ret = array('_route' => 'a_fifth'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_a_fifth; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fifth')); + } + + return $ret; + } + not_a_fifth: + + // a_sixth + if ('/a/66' === $trimmedPathinfo) { + $ret = array('_route' => 'a_sixth'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_a_sixth; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_sixth')); + } + + return $ret; + } + not_a_sixth: + + } + + // nested_wildcard + if (0 === strpos($pathinfo, '/nested') && preg_match('#^/nested/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'nested_wildcard')), array ()); + } + + if (0 === strpos($pathinfo, '/nested/group')) { + // nested_a + if ('/nested/group/a' === $trimmedPathinfo) { + $ret = array('_route' => 'nested_a'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_nested_a; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_a')); + } + + return $ret; + } + not_nested_a: + + // nested_b + if ('/nested/group/b' === $trimmedPathinfo) { + $ret = array('_route' => 'nested_b'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_nested_b; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_b')); + } + + return $ret; + } + not_nested_b: + + // nested_c + if ('/nested/group/c' === $trimmedPathinfo) { + $ret = array('_route' => 'nested_c'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_nested_c; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_c')); + } + + return $ret; + } + not_nested_c: + + } + + elseif (0 === strpos($pathinfo, '/slashed/group')) { + // slashed_a + if ('/slashed/group' === $trimmedPathinfo) { + $ret = array('_route' => 'slashed_a'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_slashed_a; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_a')); + } + + return $ret; + } + not_slashed_a: + + // slashed_b + if ('/slashed/group/b' === $trimmedPathinfo) { + $ret = array('_route' => 'slashed_b'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_slashed_b; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_b')); + } + + return $ret; + } + not_slashed_b: + + // slashed_c + if ('/slashed/group/c' === $trimmedPathinfo) { + $ret = array('_route' => 'slashed_c'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_slashed_c; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_c')); + } + + return $ret; + } + not_slashed_c: + + } + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php new file mode 100644 index 0000000..48ecdf8 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php @@ -0,0 +1,213 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + if (0 === strpos($pathinfo, '/trailing/simple')) { + // simple_trailing_slash_no_methods + if ('/trailing/simple/no-methods/' === $pathinfo) { + return array('_route' => 'simple_trailing_slash_no_methods'); + } + + // simple_trailing_slash_GET_method + if ('/trailing/simple/get-method/' === $pathinfo) { + $ret = array('_route' => 'simple_trailing_slash_GET_method'); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_simple_trailing_slash_GET_method; + } + + return $ret; + } + not_simple_trailing_slash_GET_method: + + // simple_trailing_slash_HEAD_method + if ('/trailing/simple/head-method/' === $pathinfo) { + $ret = array('_route' => 'simple_trailing_slash_HEAD_method'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_simple_trailing_slash_HEAD_method; + } + + return $ret; + } + not_simple_trailing_slash_HEAD_method: + + // simple_trailing_slash_POST_method + if ('/trailing/simple/post-method/' === $pathinfo) { + $ret = array('_route' => 'simple_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_simple_trailing_slash_POST_method; + } + + return $ret; + } + not_simple_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/trailing/regex')) { + // regex_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); + } + + // regex_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_regex_trailing_slash_GET_method; + } + + return $ret; + } + not_regex_trailing_slash_GET_method: + + // regex_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_regex_trailing_slash_HEAD_method; + } + + return $ret; + } + not_regex_trailing_slash_HEAD_method: + + // regex_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_regex_trailing_slash_POST_method; + } + + return $ret; + } + not_regex_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/simple')) { + // simple_not_trailing_slash_no_methods + if ('/not-trailing/simple/no-methods' === $pathinfo) { + return array('_route' => 'simple_not_trailing_slash_no_methods'); + } + + // simple_not_trailing_slash_GET_method + if ('/not-trailing/simple/get-method' === $pathinfo) { + $ret = array('_route' => 'simple_not_trailing_slash_GET_method'); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_simple_not_trailing_slash_GET_method; + } + + return $ret; + } + not_simple_not_trailing_slash_GET_method: + + // simple_not_trailing_slash_HEAD_method + if ('/not-trailing/simple/head-method' === $pathinfo) { + $ret = array('_route' => 'simple_not_trailing_slash_HEAD_method'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_simple_not_trailing_slash_HEAD_method; + } + + return $ret; + } + not_simple_not_trailing_slash_HEAD_method: + + // simple_not_trailing_slash_POST_method + if ('/not-trailing/simple/post-method' === $pathinfo) { + $ret = array('_route' => 'simple_not_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_simple_not_trailing_slash_POST_method; + } + + return $ret; + } + not_simple_not_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { + // regex_not_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); + } + + // regex_not_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_regex_not_trailing_slash_GET_method; + } + + return $ret; + } + not_regex_not_trailing_slash_GET_method: + + // regex_not_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_regex_not_trailing_slash_HEAD_method; + } + + return $ret; + } + not_regex_not_trailing_slash_HEAD_method: + + // regex_not_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_regex_not_trailing_slash_POST_method; + } + + return $ret; + } + not_regex_not_trailing_slash_POST_method: + + } + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php new file mode 100644 index 0000000..81d76ea --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php @@ -0,0 +1,249 @@ +context = $context; + } + + public function match($rawPathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($rawPathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request ?: $this->createRequest($pathinfo); + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + if (0 === strpos($pathinfo, '/trailing/simple')) { + // simple_trailing_slash_no_methods + if ('/trailing/simple/no-methods' === $trimmedPathinfo) { + $ret = array('_route' => 'simple_trailing_slash_no_methods'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_simple_trailing_slash_no_methods; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_no_methods')); + } + + return $ret; + } + not_simple_trailing_slash_no_methods: + + // simple_trailing_slash_GET_method + if ('/trailing/simple/get-method' === $trimmedPathinfo) { + $ret = array('_route' => 'simple_trailing_slash_GET_method'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_simple_trailing_slash_GET_method; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_GET_method')); + } + + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_simple_trailing_slash_GET_method; + } + + return $ret; + } + not_simple_trailing_slash_GET_method: + + // simple_trailing_slash_HEAD_method + if ('/trailing/simple/head-method/' === $pathinfo) { + $ret = array('_route' => 'simple_trailing_slash_HEAD_method'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_simple_trailing_slash_HEAD_method; + } + + return $ret; + } + not_simple_trailing_slash_HEAD_method: + + // simple_trailing_slash_POST_method + if ('/trailing/simple/post-method/' === $pathinfo) { + $ret = array('_route' => 'simple_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_simple_trailing_slash_POST_method; + } + + return $ret; + } + not_simple_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/trailing/regex')) { + // regex_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/?$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_regex_trailing_slash_no_methods; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_no_methods')); + } + + return $ret; + } + not_regex_trailing_slash_no_methods: + + // regex_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/?$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_regex_trailing_slash_GET_method; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_GET_method')); + } + + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_regex_trailing_slash_GET_method; + } + + return $ret; + } + not_regex_trailing_slash_GET_method: + + // regex_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_regex_trailing_slash_HEAD_method; + } + + return $ret; + } + not_regex_trailing_slash_HEAD_method: + + // regex_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_regex_trailing_slash_POST_method; + } + + return $ret; + } + not_regex_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/simple')) { + // simple_not_trailing_slash_no_methods + if ('/not-trailing/simple/no-methods' === $pathinfo) { + return array('_route' => 'simple_not_trailing_slash_no_methods'); + } + + // simple_not_trailing_slash_GET_method + if ('/not-trailing/simple/get-method' === $pathinfo) { + $ret = array('_route' => 'simple_not_trailing_slash_GET_method'); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_simple_not_trailing_slash_GET_method; + } + + return $ret; + } + not_simple_not_trailing_slash_GET_method: + + // simple_not_trailing_slash_HEAD_method + if ('/not-trailing/simple/head-method' === $pathinfo) { + $ret = array('_route' => 'simple_not_trailing_slash_HEAD_method'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_simple_not_trailing_slash_HEAD_method; + } + + return $ret; + } + not_simple_not_trailing_slash_HEAD_method: + + // simple_not_trailing_slash_POST_method + if ('/not-trailing/simple/post-method' === $pathinfo) { + $ret = array('_route' => 'simple_not_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_simple_not_trailing_slash_POST_method; + } + + return $ret; + } + not_simple_not_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { + // regex_not_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#sD', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); + } + + // regex_not_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_regex_not_trailing_slash_GET_method; + } + + return $ret; + } + not_regex_not_trailing_slash_GET_method: + + // regex_not_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_regex_not_trailing_slash_HEAD_method; + } + + return $ret; + } + not_regex_not_trailing_slash_HEAD_method: + + // regex_not_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); + goto not_regex_not_trailing_slash_POST_method; + } + + return $ret; + } + not_regex_not_trailing_slash_POST_method: + + } + + if ('/' === $pathinfo && !$allow) { + throw new Symfony\Component\Routing\Exception\NoConfigurationException(); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/empty.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/empty.yml new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/file_resource.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/file_resource.yml new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/foo.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/foo.xml new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/foo1.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/foo1.xml new file mode 100644 index 0000000..e69de29 diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml new file mode 100644 index 0000000..0d31eeb --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml new file mode 100644 index 0000000..ba3bc22 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml @@ -0,0 +1,4 @@ +bar_route: + path: /bar + defaults: + _controller: AppBundle:Bar:view diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml new file mode 100644 index 0000000..3abba1a --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml new file mode 100644 index 0000000..f7d8c67 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml @@ -0,0 +1,4 @@ +baz_route: + path: /baz + defaults: + _controller: AppBundle:Baz:view diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml new file mode 100644 index 0000000..ca6b1b5 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml new file mode 100644 index 0000000..d1ae585 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml @@ -0,0 +1,2 @@ +_static: + resource: ba?.yml diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml new file mode 100644 index 0000000..15f5698 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml new file mode 100644 index 0000000..f56ddbd --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml @@ -0,0 +1,2 @@ +_static: + resource: b?r.yml diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php new file mode 100644 index 0000000..897fa11 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php @@ -0,0 +1,7 @@ +import('php_dsl_ba?.php'); +}; diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php new file mode 100644 index 0000000..e2b91b1 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php @@ -0,0 +1,12 @@ +collection(); + + $collection->add('bar_route', '/bar') + ->defaults(array('_controller' => 'AppBundle:Bar:view')); + + return $collection; +}; diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php new file mode 100644 index 0000000..ca8f188 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php @@ -0,0 +1,12 @@ +collection(); + + $collection->add('baz_route', '/baz') + ->defaults(array('_controller' => 'AppBundle:Baz:view')); + + return $collection; +}; diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/incomplete.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/incomplete.yml new file mode 100644 index 0000000..df64d32 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/incomplete.yml @@ -0,0 +1,2 @@ +blog_show: + defaults: { _controller: MyBlogBundle:Blog:show } diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml new file mode 100644 index 0000000..f93bf9c --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml @@ -0,0 +1,20 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + 1 + 3.5 + foo + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml new file mode 100644 index 0000000..987086d --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml new file mode 100644 index 0000000..32d393c --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml new file mode 100644 index 0000000..c70e03c --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + + + + + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml new file mode 100644 index 0000000..47feb29 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml @@ -0,0 +1,20 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + 1 + 3.5 + foo + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml new file mode 100644 index 0000000..6d77065 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml new file mode 100644 index 0000000..2beee61 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml new file mode 100644 index 0000000..8fd8954 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + + + + + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_id.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_id.xml new file mode 100644 index 0000000..4ea4115 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_id.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_path.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_path.xml new file mode 100644 index 0000000..ef5bc08 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_path.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml new file mode 100644 index 0000000..e33955a --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml @@ -0,0 +1,16 @@ + + + + + + MyBundle:Blog:show + \w+ + en|fr|de + RouteCompiler + + 1 + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml new file mode 100644 index 0000000..a3e9473 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml @@ -0,0 +1,3 @@ +blog_show: + resource: validpattern.yml + path: /test diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml new file mode 100644 index 0000000..547cda3 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml @@ -0,0 +1,3 @@ +blog_show: + path: /blog/{slug} + type: custom diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml new file mode 100644 index 0000000..dc147d2 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml @@ -0,0 +1,10 @@ + + + + + + MyBundle:Blog:show + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml @@ -0,0 +1 @@ +foo diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml new file mode 100644 index 0000000..cfa9992 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml @@ -0,0 +1 @@ +route: string diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml new file mode 100644 index 0000000..015e270 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml @@ -0,0 +1,3 @@ +someroute: + resource: path/to/some.yml + name_prefix: test_ diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml new file mode 100644 index 0000000..863ef03 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml @@ -0,0 +1,8 @@ + + + + + bar + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml new file mode 100644 index 0000000..908958c --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml @@ -0,0 +1,12 @@ + + + + + + MyBundle:Blog:show + + baz + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/null_values.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/null_values.xml new file mode 100644 index 0000000..f9e2aa2 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/null_values.xml @@ -0,0 +1,12 @@ + + + + + + + foo + bar + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl.php new file mode 100644 index 0000000..0780c9f --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl.php @@ -0,0 +1,22 @@ +collection() + ->add('foo', '/foo') + ->condition('abc') + ->options(array('utf8' => true)) + ->add('buz', 'zub') + ->controller('foo:act'); + + $routes->import('php_dsl_sub.php') + ->prefix('/sub') + ->requirements(array('id' => '\d+')); + + $routes->add('ouf', '/ouf') + ->schemes(array('https')) + ->methods(array('GET')) + ->defaults(array('id' => 0)); +}; diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php new file mode 100644 index 0000000..9eb444d --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php @@ -0,0 +1,14 @@ +collection('c_') + ->prefix('pub'); + + $add('bar', '/bar'); + + $add->collection('pub_') + ->host('host') + ->add('buz', 'buz'); +}; diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml new file mode 100644 index 0000000..ecfde28 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml @@ -0,0 +1,33 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + + + 1 + + + 3.5 + + + false + + + 1 + + + 0 + + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml new file mode 100644 index 0000000..78be239 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml @@ -0,0 +1,2 @@ +"#$péß^a|": + path: "true" diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.php new file mode 100644 index 0000000..edc16d8 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.php @@ -0,0 +1,18 @@ +add('blog_show', new Route( + '/blog/{slug}', + array('_controller' => 'MyBlogBundle:Blog:show'), + array('locale' => '\w+'), + array('compiler_class' => 'RouteCompiler'), + '{locale}.example.com', + array('https'), + array('GET', 'POST', 'put', 'OpTiOnS'), + 'context.getMethod() == "GET"' +)); + +return $collection; diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.xml new file mode 100644 index 0000000..dbc72e4 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.xml @@ -0,0 +1,15 @@ + + + + + + MyBundle:Blog:show + \w+ + + context.getMethod() == "GET" + + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.yml new file mode 100644 index 0000000..565abaa --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.yml @@ -0,0 +1,13 @@ +blog_show: + path: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } + host: "{locale}.example.com" + requirements: { 'locale': '\w+' } + methods: ['GET','POST','put','OpTiOnS'] + schemes: ['https'] + condition: 'context.getMethod() == "GET"' + options: + compiler_class: RouteCompiler + +blog_show_inherited: + path: /blog/{slug} diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.php new file mode 100644 index 0000000..482c80b --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.php @@ -0,0 +1,18 @@ +import('validpattern.php'); +$collection->addDefaults(array( + 'foo' => 123, +)); +$collection->addRequirements(array( + 'foo' => '\d+', +)); +$collection->addOptions(array( + 'foo' => 'bar', +)); +$collection->setCondition('context.getMethod() == "POST"'); +$collection->addPrefix('/prefix'); + +return $collection; diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.xml new file mode 100644 index 0000000..b7a15dd --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.xml @@ -0,0 +1,13 @@ + + + + + + 123 + \d+ + + context.getMethod() == "POST" + + diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.yml new file mode 100644 index 0000000..faf2263 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.yml @@ -0,0 +1,8 @@ +_blog: + resource: validpattern.yml + prefix: /{foo} + defaults: { 'foo': '123' } + requirements: { 'foo': '\d+' } + options: { 'foo': 'bar' } + host: "" + condition: 'context.getMethod() == "POST"' diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php new file mode 100644 index 0000000..5871420 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php @@ -0,0 +1,5 @@ + + + diff --git a/assets/php/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/assets/php/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php new file mode 100644 index 0000000..f84802b --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; +use Symfony\Component\Routing\RequestContext; + +class PhpGeneratorDumperTest extends TestCase +{ + /** + * @var RouteCollection + */ + private $routeCollection; + + /** + * @var PhpGeneratorDumper + */ + private $generatorDumper; + + /** + * @var string + */ + private $testTmpFilepath; + + /** + * @var string + */ + private $largeTestTmpFilepath; + + protected function setUp() + { + parent::setUp(); + + $this->routeCollection = new RouteCollection(); + $this->generatorDumper = new PhpGeneratorDumper($this->routeCollection); + $this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php'; + $this->largeTestTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php'; + @unlink($this->testTmpFilepath); + @unlink($this->largeTestTmpFilepath); + } + + protected function tearDown() + { + parent::tearDown(); + + @unlink($this->testTmpFilepath); + + $this->routeCollection = null; + $this->generatorDumper = null; + $this->testTmpFilepath = null; + } + + public function testDumpWithRoutes() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + $this->routeCollection->add('Test2', new Route('/testing2')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump()); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \ProjectUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter); + $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter); + $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter); + $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter); + } + + public function testDumpWithTooManyRoutes() + { + if (defined('HHVM_VERSION_ID')) { + $this->markTestSkipped('HHVM consumes too much memory on this test.'); + } + + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + for ($i = 0; $i < 32769; ++$i) { + $this->routeCollection->add('route_'.$i, new Route('/route_'.$i)); + } + $this->routeCollection->add('Test2', new Route('/testing2')); + + file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump(array( + 'class' => 'ProjectLargeUrlGenerator', + ))); + $this->routeCollection = $this->generatorDumper = null; + include $this->largeTestTmpFilepath; + + $projectUrlGenerator = new \ProjectLargeUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter); + $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter); + $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter); + $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testDumpWithoutRoutes() + { + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'WithoutRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \WithoutRoutesUrlGenerator(new RequestContext('/app.php')); + + $projectUrlGenerator->generate('Test', array()); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateNonExistingRoute() + { + $this->routeCollection->add('Test', new Route('/test')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'NonExistingRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \NonExistingRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('NonExisting', array()); + } + + public function testDumpForRouteWithDefaults() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}', array('foo' => 'bar'))); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'DefaultRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \DefaultRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('Test', array()); + + $this->assertEquals('/testing', $url); + } + + public function testDumpWithSchemeRequirement() + { + $this->routeCollection->add('Test1', new Route('/testing', array(), array(), array(), '', array('ftp', 'https'))); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'SchemeUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('ftp://localhost/app.php/testing', $absoluteUrl); + $this->assertEquals('ftp://localhost/app.php/testing', $relativeUrl); + + $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php', 'GET', 'localhost', 'https')); + + $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl); + $this->assertEquals('/app.php/testing', $relativeUrl); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php b/assets/php/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php new file mode 100644 index 0000000..68add77 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php @@ -0,0 +1,724 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RequestContext; + +class UrlGeneratorTest extends TestCase +{ + public function testAbsoluteUrlWithPort80() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithPort443() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('https://localhost/app.php/testing', $url); + } + + public function testAbsoluteUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpPort' => 8080))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost:8080/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpsPort' => 8080, 'scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('https://localhost:8080/app.php/testing', $url); + } + + public function testRelativeUrlWithoutParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing', $url); + } + + public function testRelativeUrlWithParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testRelativeUrlWithNullParameter() + { + $routes = $this->getRoutes('test', new Route('/testing.{format}', array('format' => null))); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRelativeUrlWithNullParameterButNotOptional() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}/bar', array('foo' => null))); + // This must raise an exception because the default requirement for "foo" is "[^/]+" which is not met with these params. + // Generating path "/testing//bar" would be wrong as matching this route would fail. + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function testRelativeUrlWithOptionalZeroParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{page}')); + $url = $this->getGenerator($routes)->generate('test', array('page' => 0), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing/0', $url); + } + + public function testNotPassedOptionalParameterInBetween() + { + $routes = $this->getRoutes('test', new Route('/{slug}/{page}', array('slug' => 'index', 'page' => 0))); + $this->assertSame('/app.php/index/1', $this->getGenerator($routes)->generate('test', array('page' => 1))); + $this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test')); + } + + public function testRelativeUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testAbsoluteUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing?foo=bar', $url); + } + + public function testUrlWithNullExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => null), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testUrlWithExtraParametersFromGlobals() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('bar', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array('foo' => 'bar')); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testUrlWithGlobalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('foo', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testGlobalParameterHasHigherPriorityThanDefault() + { + $routes = $this->getRoutes('test', new Route('/{_locale}', array('_locale' => 'en'))); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('_locale', 'de'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertSame('/app.php/de', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateWithoutRoutes() + { + $routes = $this->getRoutes('foo', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException + */ + public function testGenerateForRouteWithoutMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidOptionalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '1|2'))); + $this->getGenerator($routes)->generate('test', array('foo' => '0'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrict() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrictWithLogger() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once()) + ->method('error'); + $generator = $this->getGenerator($routes, array(), $logger); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testGenerateForRouteWithInvalidParameterButDisabledRequirementsCheck() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(null); + $this->assertSame('/app.php/testing/bar', $generator->generate('test', array('foo' => 'bar'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidUtf8Parameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '\pL+'), array('utf8' => true))); + $this->getGenerator($routes)->generate('test', array('foo' => 'abc123'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRequiredParamAndEmptyPassed() + { + $routes = $this->getRoutes('test', new Route('/{slug}', array(), array('slug' => '.+'))); + $this->getGenerator($routes)->generate('test', array('slug' => '')); + } + + public function testSchemeRequirementDoesNothingIfSameCurrentScheme() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testSchemeRequirementForcesAbsoluteUrl() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https'))); + $this->assertEquals('https://localhost/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http'))); + $this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testSchemeRequirementCreatesUrlForFirstRequiredScheme() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('Ftp', 'https'))); + $this->assertEquals('ftp://localhost/app.php/', $this->getGenerator($routes)->generate('test')); + } + + public function testPathWithTwoStartingSlashes() + { + $routes = $this->getRoutes('test', new Route('//path-and-not-domain')); + + // this must not generate '//path-and-not-domain' because that would be a network path + $this->assertSame('/path-and-not-domain', $this->getGenerator($routes, array('BaseUrl' => ''))->generate('test')); + } + + public function testNoTrailingSlashForMultipleOptionalParameters() + { + $routes = $this->getRoutes('test', new Route('/category/{slug1}/{slug2}/{slug3}', array('slug2' => null, 'slug3' => null))); + + $this->assertEquals('/app.php/category/foo', $this->getGenerator($routes)->generate('test', array('slug1' => 'foo'))); + } + + public function testWithAnIntegerAsADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo'))); + } + + public function testNullForOptionalParameterIsIgnored() + { + $routes = $this->getRoutes('test', new Route('/test/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => null))); + } + + public function testQueryParamSameAsDefault() + { + $routes = $this->getRoutes('test', new Route('/test', array('page' => 1))); + + $this->assertSame('/app.php/test?page=2', $this->getGenerator($routes)->generate('test', array('page' => 2))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => 1))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => '1'))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test')); + } + + public function testArrayQueryParamSameAsDefault() + { + $routes = $this->getRoutes('test', new Route('/test', array('array' => array('foo', 'bar')))); + + $this->assertSame('/app.php/test?array%5B0%5D=bar&array%5B1%5D=foo', $this->getGenerator($routes)->generate('test', array('array' => array('bar', 'foo')))); + $this->assertSame('/app.php/test?array%5Ba%5D=foo&array%5Bb%5D=bar', $this->getGenerator($routes)->generate('test', array('array' => array('a' => 'foo', 'b' => 'bar')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array('foo', 'bar')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array(1 => 'bar', 0 => 'foo')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test')); + } + + public function testGenerateWithSpecialRouteName() + { + $routes = $this->getRoutes('$péß^a|', new Route('/bar')); + + $this->assertSame('/app.php/bar', $this->getGenerator($routes)->generate('$péß^a|')); + } + + public function testUrlEncoding() + { + $expectedPath = '/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'?query=%40%3A%5B%5D/%28%29%2A%27%22%20%2B%2C%3B-._~%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id'; + + // This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986) + // and other special ASCII chars. These chars are tested as static text path, variable path and query param. + $chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id'; + $routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+'))); + $this->assertSame($expectedPath, $this->getGenerator($routes)->generate('test', array( + 'varpath' => $chars, + 'query' => $chars, + ))); + } + + public function testEncodingOfRelativePathSegments() + { + $routes = $this->getRoutes('test', new Route('/dir/../dir/..')); + $this->assertSame('/app.php/dir/%2E%2E/dir/%2E%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/dir/./dir/.')); + $this->assertSame('/app.php/dir/%2E/dir/%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/a./.a/a../..a/...')); + $this->assertSame('/app.php/a./.a/a../..a/...', $this->getGenerator($routes)->generate('test')); + } + + public function testAdjacentVariables() + { + $routes = $this->getRoutes('test', new Route('/{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '\d+'))); + $generator = $this->getGenerator($routes); + $this->assertSame('/app.php/foo123', $generator->generate('test', array('x' => 'foo', 'y' => '123'))); + $this->assertSame('/app.php/foo123bar.xml', $generator->generate('test', array('x' => 'foo', 'y' => '123', 'z' => 'bar', '_format' => 'xml'))); + + // The default requirement for 'x' should not allow the separator '.' in this case because it would otherwise match everything + // and following optional variables like _format could never match. + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\InvalidParameterException'); + $generator->generate('test', array('x' => 'do.t', 'y' => '123', 'z' => 'bar', '_format' => 'xml')); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}', array('what' => 'All'))); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/get', $generator->generate('test')); + $this->assertSame('/app.php/getSites', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}Suffix')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/getSitesSuffix', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testDefaultRequirementOfVariable() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'index', '_format' => 'sl/ash')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html')); + } + + public function testWithHostDifferentFromContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' => 'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContextAndAbsolute() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHost() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHostWhenParamHasADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterEqualsDefaultValueInHost() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function testUrlWithInvalidParameterInHostInNonStrictMode() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH)); + } + + public function testHostIsCaseInsensitive() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('locale' => 'en|de|fr'), array(), '{locale}.FooBar.com')); + $generator = $this->getGenerator($routes); + $this->assertSame('//EN.FooBar.com/app.php/', $generator->generate('test', array('locale' => 'EN'), UrlGeneratorInterface::NETWORK_PATH)); + } + + public function testDefaultHostIsUsedWhenContextHostIsEmpty() + { + $routes = $this->getRoutes('test', new Route('/route', array('domain' => 'my.fallback.host'), array('domain' => '.+'), array(), '{domain}', array('http'))); + + $generator = $this->getGenerator($routes); + $generator->getContext()->setHost(''); + + $this->assertSame('http://my.fallback.host/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testDefaultHostIsUsedWhenContextHostIsEmptyAndSchemeIsNot() + { + $routes = $this->getRoutes('test', new Route('/route', array('domain' => 'my.fallback.host'), array('domain' => '.+'), array(), '{domain}', array('http', 'https'))); + + $generator = $this->getGenerator($routes); + $generator->getContext()->setHost(''); + $generator->getContext()->setScheme('https'); + + $this->assertSame('https://my.fallback.host/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testAbsoluteUrlFallbackToRelativeIfHostIsEmptyAndSchemeIsNot() + { + $routes = $this->getRoutes('test', new Route('/route', array(), array(), array(), '', array('http', 'https'))); + + $generator = $this->getGenerator($routes); + $generator->getContext()->setHost(''); + $generator->getContext()->setScheme('https'); + + $this->assertSame('/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testGenerateNetworkPath() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com', array('http'))); + + $this->assertSame('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'network path with different host' + ); + $this->assertSame('//fr.example.com/app.php/Fabien?query=string', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', + array('name' => 'Fabien', 'locale' => 'fr', 'query' => 'string'), UrlGeneratorInterface::NETWORK_PATH), 'network path although host same as context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'absolute URL because scheme requirement does not match context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL), 'absolute URL with same scheme because it is requested' + ); + } + + public function testGenerateRelativePath() + { + $routes = new RouteCollection(); + $routes->add('article', new Route('/{author}/{article}/')); + $routes->add('comments', new Route('/{author}/{article}/comments')); + $routes->add('host', new Route('/{article}', array(), array(), array(), '{author}.example.com')); + $routes->add('scheme', new Route('/{author}/blog', array(), array(), array(), '', array('https'))); + $routes->add('unrelated', new Route('/about')); + + $generator = $this->getGenerator($routes, array('host' => 'example.com', 'pathInfo' => '/fabien/symfony-is-great/')); + + $this->assertSame('comments', $generator->generate('comments', + array('author' => 'fabien', 'article' => 'symfony-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('comments?page=2', $generator->generate('comments', + array('author' => 'fabien', 'article' => 'symfony-is-great', 'page' => 2), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../twig-is-great/', $generator->generate('article', + array('author' => 'fabien', 'article' => 'twig-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../bernhard/forms-are-great/', $generator->generate('article', + array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('//bernhard.example.com/app.php/forms-are-great', $generator->generate('host', + array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('https://example.com/app.php/bernhard/blog', $generator->generate('scheme', + array('author' => 'bernhard'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../about', $generator->generate('unrelated', + array(), UrlGeneratorInterface::RELATIVE_PATH) + ); + } + + /** + * @dataProvider provideRelativePaths + */ + public function testGetRelativePath($sourcePath, $targetPath, $expectedPath) + { + $this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath)); + } + + public function provideRelativePaths() + { + return array( + array( + '/same/dir/', + '/same/dir/', + '', + ), + array( + '/same/file', + '/same/file', + '', + ), + array( + '/', + '/file', + 'file', + ), + array( + '/', + '/dir/file', + 'dir/file', + ), + array( + '/dir/file.html', + '/dir/different-file.html', + 'different-file.html', + ), + array( + '/same/dir/extra-file', + '/same/dir/', + './', + ), + array( + '/parent/dir/', + '/parent/', + '../', + ), + array( + '/parent/dir/extra-file', + '/parent/', + '../', + ), + array( + '/a/b/', + '/x/y/z/', + '../../x/y/z/', + ), + array( + '/a/b/c/d/e', + '/a/c/d', + '../../../c/d', + ), + array( + '/a/b/c//', + '/a/b/c/', + '../', + ), + array( + '/a/b/c/', + '/a/b/c//', + './/', + ), + array( + '/root/a/b/c/', + '/root/x/b/c/', + '../../../x/b/c/', + ), + array( + '/a/b/c/d/', + '/a', + '../../../../a', + ), + array( + '/special-chars/sp%20ce/1€/mäh/e=mc²', + '/special-chars/sp%20ce/1€/<µ>/e=mc²', + '../<µ>/e=mc²', + ), + array( + 'not-rooted', + 'dir/file', + 'dir/file', + ), + array( + '//dir/', + '', + '../../', + ), + array( + '/dir/', + '/dir/file:with-colon', + './file:with-colon', + ), + array( + '/dir/', + '/dir/subdir/file:with-colon', + 'subdir/file:with-colon', + ), + array( + '/dir/', + '/dir/:subdir/', + './:subdir/', + ), + ); + } + + public function testFragmentsCanBeAppendedToUrls() + { + $routes = $this->getRoutes('test', new Route('/testing')); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => 'frag ment'), UrlGeneratorInterface::ABSOLUTE_PATH); + $this->assertEquals('/app.php/testing#frag%20ment', $url); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '0'), UrlGeneratorInterface::ABSOLUTE_PATH); + $this->assertEquals('/app.php/testing#0', $url); + } + + public function testFragmentsDoNotEscapeValidCharacters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '?/'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing#?/', $url); + } + + public function testFragmentsCanBeDefinedAsDefaults() + { + $routes = $this->getRoutes('test', new Route('/testing', array('_fragment' => 'fragment'))); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing#fragment', $url); + } + + protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) + { + $context = new RequestContext('/app.php'); + foreach ($parameters as $key => $value) { + $method = 'set'.$key; + $context->$method($value); + } + + return new UrlGenerator($routes, $context, $logger); + } + + protected function getRoutes($name, Route $route) + { + $routes = new RouteCollection(); + $routes->add($name, $route); + + return $routes; + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php new file mode 100644 index 0000000..e8bbe8f --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; + +abstract class AbstractAnnotationLoaderTest extends TestCase +{ + public function getReader() + { + return $this->getMockBuilder('Doctrine\Common\Annotations\Reader') + ->disableOriginalConstructor() + ->getMock() + ; + } + + public function getClassLoader($reader) + { + return $this->getMockBuilder('Symfony\Component\Routing\Loader\AnnotationClassLoader') + ->setConstructorArgs(array($reader)) + ->getMockForAbstractClass() + ; + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php new file mode 100644 index 0000000..70db1cc --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Annotation\Route; + +class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + private $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = $this->getClassLoader($this->reader); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadMissingClass() + { + $this->loader->load('MissingClass'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadAbstractClass() + { + $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\AbstractClass'); + } + + /** + * @dataProvider provideTestSupportsChecksResource + */ + public function testSupportsChecksResource($resource, $expectedSupports) + { + $this->assertSame($expectedSupports, $this->loader->supports($resource), '->supports() returns true if the resource is loadable'); + } + + public function provideTestSupportsChecksResource() + { + return array( + array('class', true), + array('\fully\qualified\class\name', true), + array('namespaced\class\without\leading\slash', true), + array('ÿClassWithLegalSpecialCharacters', true), + array('5', false), + array('foo.foo', false), + array(null, false), + ); + } + + public function testSupportsChecksTypeIfSpecified() + { + $this->assertTrue($this->loader->supports('class', 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports('class', 'foo'), '->supports() checks the resource type if specified'); + } + + public function getLoadTests() + { + return array( + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('name' => 'route1', 'path' => '/path'), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('defaults' => array('arg2' => 'foo'), 'requirements' => array('arg3' => '\w+')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('options' => array('foo' => 'bar')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('schemes' => array('https'), 'methods' => array('GET')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('condition' => 'context.getMethod() == "GET"'), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + ); + } + + /** + * @dataProvider getLoadTests + */ + public function testLoad($className, $routeData = array(), $methodArgs = array()) + { + $routeData = array_replace(array( + 'name' => 'route', + 'path' => '/', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'condition' => '', + ), $routeData); + + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($routeData)))) + ; + + $routeCollection = $this->loader->load($className); + $route = $routeCollection->get($routeData['name']); + + $this->assertSame($routeData['path'], $route->getPath(), '->load preserves path annotation'); + $this->assertCount( + count($routeData['requirements']), + array_intersect_assoc($routeData['requirements'], $route->getRequirements()), + '->load preserves requirements annotation' + ); + $this->assertCount( + count($routeData['options']), + array_intersect_assoc($routeData['options'], $route->getOptions()), + '->load preserves options annotation' + ); + $this->assertCount( + count($routeData['defaults']), + $route->getDefaults(), + '->load preserves defaults annotation' + ); + $this->assertEquals($routeData['schemes'], $route->getSchemes(), '->load preserves schemes annotation'); + $this->assertEquals($routeData['methods'], $route->getMethods(), '->load preserves methods annotation'); + $this->assertSame($routeData['condition'], $route->getCondition(), '->load preserves condition annotation'); + } + + public function testClassRouteLoad() + { + $classRouteData = array( + 'name' => 'prefix_', + 'path' => '/prefix', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $methodRouteData = array( + 'name' => 'route1', + 'path' => '/path', + 'schemes' => array('http'), + 'methods' => array('POST', 'PUT'), + ); + + $this->reader + ->expects($this->once()) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData)))) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass'); + $route = $routeCollection->get($classRouteData['name'].$methodRouteData['name']); + + $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods'); + } + + public function testInvokableClassRouteLoad() + { + $classRouteData = array( + 'name' => 'route1', + 'path' => '/', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $this->reader + ->expects($this->exactly(2)) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); + $route = $routeCollection->get($classRouteData['name']); + + $this->assertSame($classRouteData['path'], $route->getPath(), '->load preserves class route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $classRouteData['schemes']), $route->getSchemes(), '->load preserves class route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $classRouteData['methods']), $route->getMethods(), '->load preserves class route methods'); + } + + public function testInvokableClassWithMethodRouteLoad() + { + $classRouteData = array( + 'name' => 'route1', + 'path' => '/prefix', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $methodRouteData = array( + 'name' => 'route2', + 'path' => '/path', + 'schemes' => array('http'), + 'methods' => array('POST', 'PUT'), + ); + + $this->reader + ->expects($this->once()) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData)))) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); + $route = $routeCollection->get($classRouteData['name']); + + $this->assertNull($route, '->load ignores class route'); + + $route = $routeCollection->get($classRouteData['name'].$methodRouteData['name']); + + $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods'); + } + + private function getAnnotatedRoute($data) + { + return new Route($data); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php new file mode 100644 index 0000000..1e8ee39 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; +use Symfony\Component\Config\FileLocator; + +class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationDirectoryLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->exactly(4))->method('getClassAnnotation'); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); + } + + public function testLoadIgnoresHiddenDirectories() + { + $this->expectAnnotationsToBeReadFrom(array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\FooClass', + )); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); + } + + public function testSupports() + { + $fixturesDir = __DIR__.'/../Fixtures'; + + $this->assertTrue($this->loader->supports($fixturesDir), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixturesDir, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports() checks the resource type if specified'); + } + + public function testItSupportsAnyAnnotation() + { + $this->assertTrue($this->loader->supports(__DIR__.'/../Fixtures/even-with-not-existing-folder', 'annotation')); + } + + public function testLoadFileIfLocatedResourceIsFile() + { + $this->reader->expects($this->exactly(1))->method('getClassAnnotation'); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php'); + } + + private function expectAnnotationsToBeReadFrom(array $classes) + { + $this->reader->expects($this->exactly(count($classes))) + ->method('getClassAnnotation') + ->with($this->callback(function (\ReflectionClass $class) use ($classes) { + return in_array($class->getName(), $classes); + })); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php new file mode 100644 index 0000000..7f1d576 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Annotation\Route; + +class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationFileLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->once())->method('getClassAnnotation'); + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php'); + } + + /** + * @requires PHP 5.4 + */ + public function testLoadTraitWithClassConstant() + { + $this->reader->expects($this->never())->method('getClassAnnotation'); + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooTrait.php'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Did you forgot to add the "loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/NoStartTagClass.php'); + } + + /** + * @requires PHP 5.6 + */ + public function testLoadVariadic() + { + $route = new Route(array('path' => '/path/to/{id}')); + $this->reader->expects($this->once())->method('getClassAnnotation'); + $this->reader->expects($this->once())->method('getMethodAnnotations') + ->will($this->returnValue(array($route))); + + $this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/VariadicClass.php'); + } + + /** + * @requires PHP 7.0 + */ + public function testLoadAnonymousClass() + { + $this->reader->expects($this->never())->method('getClassAnnotation'); + $this->reader->expects($this->never())->method('getMethodAnnotations'); + + $this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php'); + } + + public function testSupports() + { + $fixture = __DIR__.'/../Fixtures/annotated.php'; + + $this->assertTrue($this->loader->supports($fixture), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixture, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixture, 'foo'), '->supports() checks the resource type if specified'); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php new file mode 100644 index 0000000..5d963f8 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Loader\ClosureLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ClosureLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new ClosureLoader(); + + $closure = function () {}; + + $this->assertTrue($loader->supports($closure), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports($closure, 'closure'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports($closure, 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoad() + { + $loader = new ClosureLoader(); + + $route = new Route('/'); + $routes = $loader->load(function () use ($route) { + $routes = new RouteCollection(); + + $routes->add('foo', $route); + + return $routes; + }); + + $this->assertEquals($route, $routes->get('foo'), '->load() loads a \Closure resource'); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php new file mode 100644 index 0000000..fc29d37 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\DirectoryLoader; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\RouteCollection; + +class DirectoryLoaderTest extends AbstractAnnotationLoaderTest +{ + private $loader; + private $reader; + + protected function setUp() + { + parent::setUp(); + + $locator = new FileLocator(); + $this->reader = $this->getReader(); + $this->loader = new DirectoryLoader($locator); + $resolver = new LoaderResolver(array( + new YamlFileLoader($locator), + new AnnotationFileLoader($locator, $this->getClassLoader($this->reader)), + $this->loader, + )); + $this->loader->setResolver($resolver); + } + + public function testLoadDirectory() + { + $collection = $this->loader->load(__DIR__.'/../Fixtures/directory', 'directory'); + $this->verifyCollection($collection); + } + + public function testImportDirectory() + { + $collection = $this->loader->load(__DIR__.'/../Fixtures/directory_import', 'directory'); + $this->verifyCollection($collection); + } + + private function verifyCollection(RouteCollection $collection) + { + $routes = $collection->all(); + + $this->assertCount(3, $routes, 'Three routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + for ($i = 1; $i <= 3; ++$i) { + $this->assertSame('/route/'.$i, $routes['route'.$i]->getPath()); + } + } + + public function testSupports() + { + $fixturesDir = __DIR__.'/../Fixtures'; + + $this->assertFalse($this->loader->supports($fixturesDir), '->supports(*) returns false'); + + $this->assertTrue($this->loader->supports($fixturesDir, 'directory'), '->supports(*, "directory") returns true'); + $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports(*, "foo") returns false'); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php new file mode 100644 index 0000000..08d806a --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\GlobFileLoader; +use Symfony\Component\Routing\RouteCollection; + +class GlobFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new GlobFileLoader(new FileLocator()); + + $this->assertTrue($loader->supports('any-path', 'glob'), '->supports() returns true if the resource has the glob type'); + $this->assertFalse($loader->supports('any-path'), '->supports() returns false if the resource is not of glob type'); + } + + public function testLoadAddsTheGlobResourceToTheContainer() + { + $loader = new GlobFileLoaderWithoutImport(new FileLocator()); + $collection = $loader->load(__DIR__.'/../Fixtures/directory/*.yml'); + + $this->assertEquals(new GlobResource(__DIR__.'/../Fixtures/directory', '/*.yml', false), $collection->getResources()[0]); + } +} + +class GlobFileLoaderWithoutImport extends GlobFileLoader +{ + public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null) + { + return new RouteCollection(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php new file mode 100644 index 0000000..408fa0b --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Loader\ObjectRouteLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ObjectRouteLoaderTest extends TestCase +{ + public function testLoadCallsServiceAndReturnsCollection() + { + $loader = new ObjectRouteLoaderForTest(); + + // create a basic collection that will be returned + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $loader->loaderMap = array( + 'my_route_provider_service' => new RouteService($collection), + ); + + $actualRoutes = $loader->load( + 'my_route_provider_service:loadRoutes', + 'service' + ); + + $this->assertSame($collection, $actualRoutes); + // the service file should be listed as a resource + $this->assertNotEmpty($actualRoutes->getResources()); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getBadResourceStrings + */ + public function testExceptionWithoutSyntax($resourceString) + { + $loader = new ObjectRouteLoaderForTest(); + $loader->load($resourceString); + } + + public function getBadResourceStrings() + { + return array( + array('Foo'), + array('Bar::baz'), + array('Foo:Bar:baz'), + ); + } + + /** + * @expectedException \LogicException + */ + public function testExceptionOnNoObjectReturned() + { + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => 'NOT_AN_OBJECT'); + $loader->load('my_service:method'); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testExceptionOnBadMethod() + { + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => new \stdClass()); + $loader->load('my_service:method'); + } + + /** + * @expectedException \LogicException + */ + public function testExceptionOnMethodNotReturningCollection() + { + $service = $this->getMockBuilder('stdClass') + ->setMethods(array('loadRoutes')) + ->getMock(); + $service->expects($this->once()) + ->method('loadRoutes') + ->will($this->returnValue('NOT_A_COLLECTION')); + + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => $service); + $loader->load('my_service:loadRoutes'); + } +} + +class ObjectRouteLoaderForTest extends ObjectRouteLoader +{ + public $loaderMap = array(); + + protected function getServiceObject($id) + { + return isset($this->loaderMap[$id]) ? $this->loaderMap[$id] : null; + } +} + +class RouteService +{ + private $collection; + + public function __construct($collection) + { + $this->collection = $collection; + } + + public function loadRoutes() + { + return $this->collection; + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php new file mode 100644 index 0000000..0dcf5d4 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Loader\PhpFileLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class PhpFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new PhpFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.php'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.php', 'php'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.php', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.php'); + $routes = $routeCollection->all(); + + $this->assertCount(1, $routes, 'One route is loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testLoadWithImport() + { + $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.php'); + $routes = $routeCollection->all(); + + $this->assertCount(1, $routes, 'One route is loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/prefix/blog/{slug}', $route->getPath()); + $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testThatDefiningVariableInConfigFileHasNoSideEffects() + { + $locator = new FileLocator(array(__DIR__.'/../Fixtures')); + $loader = new PhpFileLoader($locator); + $routeCollection = $loader->load('with_define_path_variable.php'); + $resources = $routeCollection->getResources(); + $this->assertCount(1, $resources); + $this->assertContainsOnly('Symfony\Component\Config\Resource\ResourceInterface', $resources); + $fileResource = reset($resources); + $this->assertSame( + realpath($locator->locate('with_define_path_variable.php')), + (string) $fileResource + ); + } + + public function testRoutingConfigurator() + { + $locator = new FileLocator(array(__DIR__.'/../Fixtures')); + $loader = new PhpFileLoader($locator); + $routeCollection = $loader->load('php_dsl.php'); + + $expectedCollection = new RouteCollection(); + + $expectedCollection->add('foo', (new Route('/foo')) + ->setOptions(array('utf8' => true)) + ->setCondition('abc') + ); + $expectedCollection->add('buz', (new Route('/zub')) + ->setDefaults(array('_controller' => 'foo:act')) + ); + $expectedCollection->add('c_bar', (new Route('/sub/pub/bar')) + ->setRequirements(array('id' => '\d+')) + ); + $expectedCollection->add('c_pub_buz', (new Route('/sub/pub/buz')) + ->setHost('host') + ->setRequirements(array('id' => '\d+')) + ); + $expectedCollection->add('ouf', (new Route('/ouf')) + ->setSchemes(array('https')) + ->setMethods(array('GET')) + ->setDefaults(array('id' => 0)) + ); + + $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub.php'))); + $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php'))); + + $this->assertEquals($expectedCollection, $routeCollection); + } + + public function testRoutingConfiguratorCanImportGlobPatterns() + { + $locator = new FileLocator(array(__DIR__.'/../Fixtures/glob')); + $loader = new PhpFileLoader($locator); + $routeCollection = $loader->load('php_dsl.php'); + + $route = $routeCollection->get('bar_route'); + $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller')); + + $route = $routeCollection->get('baz_route'); + $this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller')); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php new file mode 100644 index 0000000..21fc340 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Routing\Tests\Fixtures\CustomXmlFileLoader; + +class XmlFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new XmlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.xml', 'xml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.xml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.xml'); + $route = $routeCollection->get('blog_show'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testLoadWithNamespacePrefix() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('namespaceprefix.xml'); + + $this->assertCount(1, $routeCollection->all(), 'One route is loaded'); + + $route = $routeCollection->get('blog_show'); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{_locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('slug')); + $this->assertSame('en|fr|de', $route->getRequirement('_locale')); + $this->assertNull($route->getDefault('slug')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertSame(1, $route->getDefault('page')); + } + + public function testLoadWithImport() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.xml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + $this->assertSame('context.getMethod() == "POST"', $route->getCondition()); + } + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFileEvenWithoutSchemaValidation($filePath) + { + $loader = new CustomXmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml'), array('missing_id.xml'), array('missing_path.xml')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load('withdoctype.xml'); + } + + public function testNullValues() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('null_values.xml'); + $route = $routeCollection->get('blog_show'); + + $this->assertTrue($route->hasDefault('foo')); + $this->assertNull($route->getDefault('foo')); + $this->assertTrue($route->hasDefault('bar')); + $this->assertNull($route->getDefault('bar')); + $this->assertEquals('foo', $route->getDefault('foobar')); + $this->assertEquals('bar', $route->getDefault('baz')); + } + + public function testScalarDataTypeDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('scalar_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'slug' => null, + 'published' => true, + 'page' => 1, + 'price' => 3.5, + 'archived' => false, + 'free' => true, + 'locked' => false, + 'foo' => null, + 'bar' => null, + ), + $route->getDefaults() + ); + } + + public function testListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(true, 1, 3.5, 'foo'), + ), + $route->getDefaults() + ); + } + + public function testListInListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_in_list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(array(true, 1, 3.5, 'foo')), + ), + $route->getDefaults() + ); + } + + public function testListInMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_in_map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array('list' => array(true, 1, 3.5, 'foo')), + ), + $route->getDefaults() + ); + } + + public function testMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + ), + ), + $route->getDefaults() + ); + } + + public function testMapInListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_in_list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + )), + ), + $route->getDefaults() + ); + } + + public function testMapInMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_in_map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array('map' => array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + )), + ), + $route->getDefaults() + ); + } + + public function testNullValuesInList() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_null_values.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame(array(null, null, null, null, null, null), $route->getDefault('list')); + } + + public function testNullValuesInMap() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_null_values.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + 'boolean' => null, + 'integer' => null, + 'float' => null, + 'string' => null, + 'list' => null, + 'map' => null, + ), + $route->getDefault('map') + ); + } + + public function testLoadRouteWithControllerAttribute() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load('routing.xml'); + + $route = $routeCollection->get('app_homepage'); + + $this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller')); + } + + public function testLoadRouteWithoutControllerAttribute() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load('routing.xml'); + + $route = $routeCollection->get('app_logout'); + + $this->assertNull($route->getDefault('_controller')); + } + + public function testLoadRouteWithControllerSetInDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load('routing.xml'); + + $route = $routeCollection->get('app_blog'); + + $this->assertSame('AppBundle:Blog:list', $route->getDefault('_controller')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for "app_blog"/ + */ + public function testOverrideControllerInDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $loader->load('override_defaults.xml'); + } + + /** + * @dataProvider provideFilesImportingRoutesWithControllers + */ + public function testImportRouteWithController($file) + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load($file); + + $route = $routeCollection->get('app_homepage'); + $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller')); + + $route = $routeCollection->get('app_blog'); + $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller')); + + $route = $routeCollection->get('app_logout'); + $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller')); + } + + public function provideFilesImportingRoutesWithControllers() + { + yield array('import_controller.xml'); + yield array('import__controller.xml'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for the "import" tag/ + */ + public function testImportWithOverriddenController() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $loader->load('import_override_defaults.xml'); + } + + public function testImportRouteWithGlobMatchingSingleFile() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob'))); + $routeCollection = $loader->load('import_single.xml'); + + $route = $routeCollection->get('bar_route'); + $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller')); + } + + public function testImportRouteWithGlobMatchingMultipleFiles() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob'))); + $routeCollection = $loader->load('import_multiple.xml'); + + $route = $routeCollection->get('bar_route'); + $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller')); + + $route = $routeCollection->get('baz_route'); + $this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller')); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php new file mode 100644 index 0000000..822bddf --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class YamlFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new YamlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.yml'), '->supports() returns true if the resource is loadable'); + $this->assertTrue($loader->supports('foo.yaml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.yml', 'yaml'), '->supports() checks the resource type if specified'); + $this->assertTrue($loader->supports('foo.yaml', 'yaml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.yml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $collection = $loader->load('empty.yml'); + + $this->assertEquals(array(), $collection->all()); + $this->assertEquals(array(new FileResource(realpath(__DIR__.'/../Fixtures/empty.yml'))), $collection->getResources()); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array( + array('nonvalid.yml'), + array('nonvalid2.yml'), + array('incomplete.yml'), + array('nonvalidkeys.yml'), + array('nonesense_resource_plus_path.yml'), + array('nonesense_type_without_resource.yml'), + array('bad_format.yml'), + ); + } + + public function testLoadSpecialRouteName() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('special_route_name.yml'); + $route = $routeCollection->get('#$péß^a|'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/true', $route->getPath()); + } + + public function testLoadWithRoute() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.yml'); + $route = $routeCollection->get('blog_show'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testLoadWithResource() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.yml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + $this->assertSame('context.getMethod() == "POST"', $route->getCondition()); + } + } + + public function testLoadRouteWithControllerAttribute() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load('routing.yml'); + + $route = $routeCollection->get('app_homepage'); + + $this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller')); + } + + public function testLoadRouteWithoutControllerAttribute() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load('routing.yml'); + + $route = $routeCollection->get('app_logout'); + + $this->assertNull($route->getDefault('_controller')); + } + + public function testLoadRouteWithControllerSetInDefaults() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load('routing.yml'); + + $route = $routeCollection->get('app_blog'); + + $this->assertSame('AppBundle:Blog:list', $route->getDefault('_controller')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "app_blog"/ + */ + public function testOverrideControllerInDefaults() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $loader->load('override_defaults.yml'); + } + + /** + * @dataProvider provideFilesImportingRoutesWithControllers + */ + public function testImportRouteWithController($file) + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $routeCollection = $loader->load($file); + + $route = $routeCollection->get('app_homepage'); + $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller')); + + $route = $routeCollection->get('app_blog'); + $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller')); + + $route = $routeCollection->get('app_logout'); + $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller')); + } + + public function provideFilesImportingRoutesWithControllers() + { + yield array('import_controller.yml'); + yield array('import__controller.yml'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "_static"/ + */ + public function testImportWithOverriddenController() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); + $loader->load('import_override_defaults.yml'); + } + + public function testImportRouteWithGlobMatchingSingleFile() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob'))); + $routeCollection = $loader->load('import_single.yml'); + + $route = $routeCollection->get('bar_route'); + $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller')); + } + + public function testImportRouteWithGlobMatchingMultipleFiles() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob'))); + $routeCollection = $loader->load('import_multiple.yml'); + + $route = $routeCollection->get('bar_route'); + $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller')); + + $route = $routeCollection->get('baz_route'); + $this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller')); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php new file mode 100644 index 0000000..cfbb524 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class DumpedRedirectableUrlMatcherTest extends RedirectableUrlMatcherTest +{ + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + static $i = 0; + + $class = 'DumpedRedirectableUrlMatcher'.++$i; + $dumper = new PhpMatcherDumper($routes); + eval('?>'.$dumper->dump(array('class' => $class, 'base_class' => 'Symfony\Component\Routing\Tests\Matcher\TestDumpedRedirectableUrlMatcher'))); + + return $this->getMockBuilder($class) + ->setConstructorArgs(array($context ?: new RequestContext())) + ->setMethods(array('redirect')) + ->getMock(); + } +} + +class TestDumpedRedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect($path, $route, $scheme = null) + { + return array(); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php new file mode 100644 index 0000000..880b2b1 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class DumpedUrlMatcherTest extends UrlMatcherTest +{ + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface. + */ + public function testSchemeRequirement() + { + parent::testSchemeRequirement(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface. + */ + public function testSchemeAndMethodMismatch() + { + parent::testSchemeRequirement(); + } + + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + static $i = 0; + + $class = 'DumpedUrlMatcher'.++$i; + $dumper = new PhpMatcherDumper($routes); + eval('?>'.$dumper->dump(array('class' => $class))); + + return new $class($context ?: new RequestContext()); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php new file mode 100644 index 0000000..823efdb --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; + +class DumperCollectionTest extends TestCase +{ + public function testGetRoot() + { + $a = new DumperCollection(); + + $b = new DumperCollection(); + $a->add($b); + + $c = new DumperCollection(); + $b->add($c); + + $d = new DumperCollection(); + $c->add($d); + + $this->assertSame($a, $c->getRoot()); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php new file mode 100644 index 0000000..f29a6d6 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -0,0 +1,459 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class PhpMatcherDumperTest extends TestCase +{ + /** + * @var string + */ + private $matcherClass; + + /** + * @var string + */ + private $dumpPath; + + protected function setUp() + { + parent::setUp(); + + $this->matcherClass = uniqid('ProjectUrlMatcher'); + $this->dumpPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_matcher.'.$this->matcherClass.'.php'; + } + + protected function tearDown() + { + parent::tearDown(); + + @unlink($this->dumpPath); + } + + /** + * @expectedException \LogicException + */ + public function testDumpWhenSchemeIsUsedWithoutAProperDumper() + { + $collection = new RouteCollection(); + $collection->add('secure', new Route( + '/secure', + array(), + array(), + array(), + '', + array('https') + )); + $dumper = new PhpMatcherDumper($collection); + $dumper->dump(); + } + + public function testRedirectPreservesUrlEncoding() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo:bar/')); + + $class = $this->generateDumpedMatcher($collection, true); + + $matcher = $this->getMockBuilder($class) + ->setMethods(array('redirect')) + ->setConstructorArgs(array(new RequestContext())) + ->getMock(); + + $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/', 'foo')->willReturn(array()); + + $matcher->match('/foo%3Abar'); + } + + /** + * @dataProvider getRouteCollections + */ + public function testDump(RouteCollection $collection, $fixture, $options = array()) + { + $basePath = __DIR__.'/../../Fixtures/dumper/'; + + $dumper = new PhpMatcherDumper($collection); + $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.'); + } + + public function getRouteCollections() + { + /* test case 1 */ + + $collection = new RouteCollection(); + + $collection->add('overridden', new Route('/overridden')); + + // defaults and requirements + $collection->add('foo', new Route( + '/foo/{bar}', + array('def' => 'test'), + array('bar' => 'baz|symfony') + )); + // method requirement + $collection->add('bar', new Route( + '/bar/{foo}', + array(), + array(), + array(), + '', + array(), + array('GET', 'head') + )); + // GET method requirement automatically adds HEAD as valid + $collection->add('barhead', new Route( + '/barhead/{foo}', + array(), + array(), + array(), + '', + array(), + array('GET') + )); + // simple + $collection->add('baz', new Route( + '/test/baz' + )); + // simple with extension + $collection->add('baz2', new Route( + '/test/baz.html' + )); + // trailing slash + $collection->add('baz3', new Route( + '/test/baz3/' + )); + // trailing slash with variable + $collection->add('baz4', new Route( + '/test/{foo}/' + )); + // trailing slash and method + $collection->add('baz5', new Route( + '/test/{foo}/', + array(), + array(), + array(), + '', + array(), + array('post') + )); + // complex name + $collection->add('baz.baz6', new Route( + '/test/{foo}/', + array(), + array(), + array(), + '', + array(), + array('put') + )); + // defaults without variable + $collection->add('foofoo', new Route( + '/foofoo', + array('def' => 'test') + )); + // pattern with quotes + $collection->add('quoter', new Route( + '/{quoter}', + array(), + array('quoter' => '[\']+') + )); + // space in pattern + $collection->add('space', new Route( + '/spa ce' + )); + + // prefixes + $collection1 = new RouteCollection(); + $collection1->add('overridden', new Route('/overridden1')); + $collection1->add('foo1', new Route('/{foo}')); + $collection1->add('bar1', new Route('/{bar}')); + $collection1->addPrefix('/b\'b'); + $collection2 = new RouteCollection(); + $collection2->addCollection($collection1); + $collection2->add('overridden', new Route('/{var}', array(), array('var' => '.*'))); + $collection1 = new RouteCollection(); + $collection1->add('foo2', new Route('/{foo1}')); + $collection1->add('bar2', new Route('/{bar1}')); + $collection1->addPrefix('/b\'b'); + $collection2->addCollection($collection1); + $collection2->addPrefix('/a'); + $collection->addCollection($collection2); + + // overridden through addCollection() and multiple sub-collections with no own prefix + $collection1 = new RouteCollection(); + $collection1->add('overridden2', new Route('/old')); + $collection1->add('helloWorld', new Route('/hello/{who}', array('who' => 'World!'))); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('overridden2', new Route('/new')); + $collection3->add('hey', new Route('/hey/')); + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + $collection1->addPrefix('/multi'); + $collection->addCollection($collection1); + + // "dynamic" prefix + $collection1 = new RouteCollection(); + $collection1->add('foo3', new Route('/{foo}')); + $collection1->add('bar3', new Route('/{bar}')); + $collection1->addPrefix('/b'); + $collection1->addPrefix('{_locale}'); + $collection->addCollection($collection1); + + // route between collections + $collection->add('ababa', new Route('/ababa')); + + // collection with static prefix but only one route + $collection1 = new RouteCollection(); + $collection1->add('foo4', new Route('/{foo}')); + $collection1->addPrefix('/aba'); + $collection->addCollection($collection1); + + // prefix and host + + $collection1 = new RouteCollection(); + + $route1 = new Route('/route1', array(), array(), array(), 'a.example.com'); + $collection1->add('route1', $route1); + + $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com'); + $collection1->add('route2', $route2); + + $route3 = new Route('/c2/route3', array(), array(), array(), 'b.example.com'); + $collection1->add('route3', $route3); + + $route4 = new Route('/route4', array(), array(), array(), 'a.example.com'); + $collection1->add('route4', $route4); + + $route5 = new Route('/route5', array(), array(), array(), 'c.example.com'); + $collection1->add('route5', $route5); + + $route6 = new Route('/route6', array(), array(), array(), null); + $collection1->add('route6', $route6); + + $collection->addCollection($collection1); + + // host and variables + + $collection1 = new RouteCollection(); + + $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route11', $route11); + + $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route12', $route12); + + $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route13', $route13); + + $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route14', $route14); + + $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com'); + $collection1->add('route15', $route15); + + $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null); + $collection1->add('route16', $route16); + + $route17 = new Route('/route17', array(), array(), array(), null); + $collection1->add('route17', $route17); + + $collection->addCollection($collection1); + + // multiple sub-collections with a single route and a prefix each + $collection1 = new RouteCollection(); + $collection1->add('a', new Route('/a...')); + $collection2 = new RouteCollection(); + $collection2->add('b', new Route('/{var}')); + $collection3 = new RouteCollection(); + $collection3->add('c', new Route('/{var}')); + $collection3->addPrefix('/c'); + $collection2->addCollection($collection3); + $collection2->addPrefix('/b'); + $collection1->addCollection($collection2); + $collection1->addPrefix('/a'); + $collection->addCollection($collection1); + + /* test case 2 */ + + $redirectCollection = clone $collection; + + // force HTTPS redirection + $redirectCollection->add('secure', new Route( + '/secure', + array(), + array(), + array(), + '', + array('https') + )); + + // force HTTP redirection + $redirectCollection->add('nonsecure', new Route( + '/nonsecure', + array(), + array(), + array(), + '', + array('http') + )); + + /* test case 3 */ + + $rootprefixCollection = new RouteCollection(); + $rootprefixCollection->add('static', new Route('/test')); + $rootprefixCollection->add('dynamic', new Route('/{var}')); + $rootprefixCollection->addPrefix('rootprefix'); + $route = new Route('/with-condition'); + $route->setCondition('context.getMethod() == "GET"'); + $rootprefixCollection->add('with-condition', $route); + + /* test case 4 */ + $headMatchCasesCollection = new RouteCollection(); + $headMatchCasesCollection->add('just_head', new Route( + '/just_head', + array(), + array(), + array(), + '', + array(), + array('HEAD') + )); + $headMatchCasesCollection->add('head_and_get', new Route( + '/head_and_get', + array(), + array(), + array(), + '', + array(), + array('HEAD', 'GET') + )); + $headMatchCasesCollection->add('get_and_head', new Route( + '/get_and_head', + array(), + array(), + array(), + '', + array(), + array('GET', 'HEAD') + )); + $headMatchCasesCollection->add('post_and_head', new Route( + '/post_and_head', + array(), + array(), + array(), + '', + array(), + array('POST', 'HEAD') + )); + $headMatchCasesCollection->add('put_and_post', new Route( + '/put_and_post', + array(), + array(), + array(), + '', + array(), + array('PUT', 'POST') + )); + $headMatchCasesCollection->add('put_and_get_and_head', new Route( + '/put_and_post', + array(), + array(), + array(), + '', + array(), + array('PUT', 'GET', 'HEAD') + )); + + /* test case 5 */ + $groupOptimisedCollection = new RouteCollection(); + $groupOptimisedCollection->add('a_first', new Route('/a/11')); + $groupOptimisedCollection->add('a_second', new Route('/a/22')); + $groupOptimisedCollection->add('a_third', new Route('/a/333')); + $groupOptimisedCollection->add('a_wildcard', new Route('/{param}')); + $groupOptimisedCollection->add('a_fourth', new Route('/a/44/')); + $groupOptimisedCollection->add('a_fifth', new Route('/a/55/')); + $groupOptimisedCollection->add('a_sixth', new Route('/a/66/')); + $groupOptimisedCollection->add('nested_wildcard', new Route('/nested/{param}')); + $groupOptimisedCollection->add('nested_a', new Route('/nested/group/a/')); + $groupOptimisedCollection->add('nested_b', new Route('/nested/group/b/')); + $groupOptimisedCollection->add('nested_c', new Route('/nested/group/c/')); + + $groupOptimisedCollection->add('slashed_a', new Route('/slashed/group/')); + $groupOptimisedCollection->add('slashed_b', new Route('/slashed/group/b/')); + $groupOptimisedCollection->add('slashed_c', new Route('/slashed/group/c/')); + + $trailingSlashCollection = new RouteCollection(); + $trailingSlashCollection->add('simple_trailing_slash_no_methods', new Route('/trailing/simple/no-methods/', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('simple_trailing_slash_GET_method', new Route('/trailing/simple/get-method/', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('simple_trailing_slash_HEAD_method', new Route('/trailing/simple/head-method/', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('simple_trailing_slash_POST_method', new Route('/trailing/simple/post-method/', array(), array(), array(), '', array(), array('POST'))); + $trailingSlashCollection->add('regex_trailing_slash_no_methods', new Route('/trailing/regex/no-methods/{param}/', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('regex_trailing_slash_GET_method', new Route('/trailing/regex/get-method/{param}/', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('regex_trailing_slash_HEAD_method', new Route('/trailing/regex/head-method/{param}/', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('regex_trailing_slash_POST_method', new Route('/trailing/regex/post-method/{param}/', array(), array(), array(), '', array(), array('POST'))); + + $trailingSlashCollection->add('simple_not_trailing_slash_no_methods', new Route('/not-trailing/simple/no-methods', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('simple_not_trailing_slash_GET_method', new Route('/not-trailing/simple/get-method', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('simple_not_trailing_slash_HEAD_method', new Route('/not-trailing/simple/head-method', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('simple_not_trailing_slash_POST_method', new Route('/not-trailing/simple/post-method', array(), array(), array(), '', array(), array('POST'))); + $trailingSlashCollection->add('regex_not_trailing_slash_no_methods', new Route('/not-trailing/regex/no-methods/{param}', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('regex_not_trailing_slash_GET_method', new Route('/not-trailing/regex/get-method/{param}', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('regex_not_trailing_slash_HEAD_method', new Route('/not-trailing/regex/head-method/{param}', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', array(), array(), array(), '', array(), array('POST'))); + + return array( + array(new RouteCollection(), 'url_matcher0.php', array()), + array($collection, 'url_matcher1.php', array()), + array($redirectCollection, 'url_matcher2.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + array($rootprefixCollection, 'url_matcher3.php', array()), + array($headMatchCasesCollection, 'url_matcher4.php', array()), + array($groupOptimisedCollection, 'url_matcher5.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + array($trailingSlashCollection, 'url_matcher6.php', array()), + array($trailingSlashCollection, 'url_matcher7.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + ); + } + + /** + * @param $dumper + */ + private function generateDumpedMatcher(RouteCollection $collection, $redirectableStub = false) + { + $options = array('class' => $this->matcherClass); + + if ($redirectableStub) { + $options['base_class'] = '\Symfony\Component\Routing\Tests\Matcher\Dumper\RedirectableUrlMatcherStub'; + } + + $dumper = new PhpMatcherDumper($collection); + $code = $dumper->dump($options); + + file_put_contents($this->dumpPath, $code); + include $this->dumpPath; + + return $this->matcherClass; + } +} + +abstract class RedirectableUrlMatcherStub extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect($path, $route, $scheme = null) + { + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php new file mode 100644 index 0000000..37419e7 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php @@ -0,0 +1,175 @@ +compile()->getStaticPrefix(); + $collection->addRoute($staticPrefix, $name); + } + + $collection->optimizeGroups(); + $dumped = $this->dumpCollection($collection); + $this->assertEquals($expected, $dumped); + } + + public function routeProvider() + { + return array( + 'Simple - not nested' => array( + array( + array('/', 'root'), + array('/prefix/segment/', 'prefix_segment'), + array('/leading/segment/', 'leading_segment'), + ), + << array( + array( + array('/', 'root'), + array('/prefix/segment/aa', 'prefix_segment'), + array('/prefix/segment/bb', 'leading_segment'), + ), + << array( + array( + array('/', 'root'), + array('/prefix/segment/', 'prefix_segment'), + array('/prefix/segment/bb', 'leading_segment'), + ), + << /prefix/segment prefix_segment +-> /prefix/segment/bb leading_segment +EOF + ), + 'Simple one level nesting' => array( + array( + array('/', 'root'), + array('/group/segment/', 'nested_segment'), + array('/group/thing/', 'some_segment'), + array('/group/other/', 'other_segment'), + ), + << /group/segment nested_segment +-> /group/thing some_segment +-> /group/other other_segment +EOF + ), + 'Retain matching order with groups' => array( + array( + array('/group/aa/', 'aa'), + array('/group/bb/', 'bb'), + array('/group/cc/', 'cc'), + array('/', 'root'), + array('/group/dd/', 'dd'), + array('/group/ee/', 'ee'), + array('/group/ff/', 'ff'), + ), + << /group/aa aa +-> /group/bb bb +-> /group/cc cc +/ root +/group +-> /group/dd dd +-> /group/ee ee +-> /group/ff ff +EOF + ), + 'Retain complex matching order with groups at base' => array( + array( + array('/aaa/111/', 'first_aaa'), + array('/prefixed/group/aa/', 'aa'), + array('/prefixed/group/bb/', 'bb'), + array('/prefixed/group/cc/', 'cc'), + array('/prefixed/', 'root'), + array('/prefixed/group/dd/', 'dd'), + array('/prefixed/group/ee/', 'ee'), + array('/prefixed/group/ff/', 'ff'), + array('/aaa/222/', 'second_aaa'), + array('/aaa/333/', 'third_aaa'), + ), + << /aaa/111 first_aaa +-> /aaa/222 second_aaa +-> /aaa/333 third_aaa +/prefixed +-> /prefixed/group +-> -> /prefixed/group/aa aa +-> -> /prefixed/group/bb bb +-> -> /prefixed/group/cc cc +-> /prefixed root +-> /prefixed/group +-> -> /prefixed/group/dd dd +-> -> /prefixed/group/ee ee +-> -> /prefixed/group/ff ff +EOF + ), + + 'Group regardless of segments' => array( + array( + array('/aaa-111/', 'a1'), + array('/aaa-222/', 'a2'), + array('/aaa-333/', 'a3'), + array('/group-aa/', 'g1'), + array('/group-bb/', 'g2'), + array('/group-cc/', 'g3'), + ), + << /aaa-111 a1 +-> /aaa-222 a2 +-> /aaa-333 a3 +/group- +-> /group-aa g1 +-> /group-bb g2 +-> /group-cc g3 +EOF + ), + ); + } + + private function dumpCollection(StaticPrefixCollection $collection, $prefix = '') + { + $lines = array(); + + foreach ($collection->getItems() as $item) { + if ($item instanceof StaticPrefixCollection) { + $lines[] = $prefix.$item->getPrefix(); + $lines[] = $this->dumpCollection($item, $prefix.'-> '); + } else { + $lines[] = $prefix.implode(' ', $item); + } + } + + return implode("\n", $lines); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php new file mode 100644 index 0000000..7984391 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class RedirectableUrlMatcherTest extends UrlMatcherTest +{ + public function testMissingTrailingSlash() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $matcher = $this->getUrlMatcher($coll); + $matcher->expects($this->once())->method('redirect')->will($this->returnValue(array())); + $matcher->match('/foo'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testRedirectWhenNoSlashForNonSafeMethod() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $context = new RequestContext(); + $context->setMethod('POST'); + $matcher = $this->getUrlMatcher($coll, $context); + $matcher->match('/foo'); + } + + public function testSchemeRedirectRedirectsToFirstScheme() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('FTP', 'HTTPS'))); + + $matcher = $this->getUrlMatcher($coll); + $matcher + ->expects($this->once()) + ->method('redirect') + ->with('/foo', 'foo', 'ftp') + ->will($this->returnValue(array('_route' => 'foo'))) + ; + $matcher->match('/foo'); + } + + public function testNoSchemaRedirectIfOneOfMultipleSchemesMatches() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https', 'http'))); + + $matcher = $this->getUrlMatcher($coll); + $matcher + ->expects($this->never()) + ->method('redirect'); + $matcher->match('/foo'); + } + + public function testSchemeRedirectWithParams() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{bar}', array(), array(), array(), '', array('https'))); + + $matcher = $this->getUrlMatcher($coll); + $matcher + ->expects($this->once()) + ->method('redirect') + ->with('/foo/baz', 'foo', 'https') + ->will($this->returnValue(array('redirect' => 'value'))) + ; + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'redirect' => 'value'), $matcher->match('/foo/baz')); + } + + public function testSlashRedirectWithParams() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{bar}/')); + + $matcher = $this->getUrlMatcher($coll); + $matcher + ->expects($this->once()) + ->method('redirect') + ->with('/foo/baz/', 'foo', null) + ->will($this->returnValue(array('redirect' => 'value'))) + ; + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'redirect' => 'value'), $matcher->match('/foo/baz')); + } + + public function testRedirectPreservesUrlEncoding() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo:bar/')); + + $matcher = $this->getUrlMatcher($coll); + $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/')->willReturn(array()); + $matcher->match('/foo%3Abar'); + } + + public function testSchemeRequirement() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https'))); + $matcher = $this->getUrlMatcher($coll, new RequestContext()); + $matcher->expects($this->once())->method('redirect')->with('/foo', 'foo', 'https')->willReturn(array()); + $this->assertSame(array('_route' => 'foo'), $matcher->match('/foo')); + } + + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + return $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($routes, $context ?: new RequestContext())); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php new file mode 100644 index 0000000..9f0529e --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; + +class TraceableUrlMatcherTest extends TestCase +{ + public function test() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('POST'))); + $coll->add('bar', new Route('/bar/{id}', array(), array('id' => '\d+'))); + $coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+'), array(), '', array(), array('POST'))); + $coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz')); + $coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz')); + $coll->add('bar4', new Route('/foo2', array(), array(), array(), 'baz', array(), array(), 'context.getMethod() == "GET"')); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($coll, $context); + $traces = $matcher->getTraces('/babar'); + $this->assertSame(array(0, 0, 0, 0, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo'); + $this->assertSame(array(1, 0, 0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/12'); + $this->assertSame(array(0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertSame(array(0, 1, 1, 0, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo1'); + $this->assertSame(array(0, 0, 0, 0, 2), $this->getLevels($traces)); + + $context->setMethod('POST'); + $traces = $matcher->getTraces('/foo'); + $this->assertSame(array(2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertSame(array(0, 1, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo2'); + $this->assertSame(array(0, 0, 0, 0, 0, 1), $this->getLevels($traces)); + } + + public function testMatchRouteOnMultipleHosts() + { + $routes = new RouteCollection(); + $routes->add('first', new Route( + '/mypath/', + array('_controller' => 'MainBundle:Info:first'), + array(), + array(), + 'some.example.com' + )); + + $routes->add('second', new Route( + '/mypath/', + array('_controller' => 'MainBundle:Info:second'), + array(), + array(), + 'another.example.com' + )); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($routes, $context); + + $traces = $matcher->getTraces('/mypath/'); + $this->assertSame( + array(TraceableUrlMatcher::ROUTE_ALMOST_MATCHES, TraceableUrlMatcher::ROUTE_ALMOST_MATCHES), + $this->getLevels($traces) + ); + } + + public function getLevels($traces) + { + $levels = array(); + foreach ($traces as $trace) { + $levels[] = $trace['level']; + } + + return $levels; + } + + public function testRoutesWithConditions() + { + $routes = new RouteCollection(); + $routes->add('foo', new Route('/foo', array(), array(), array(), 'baz', array(), array(), "request.headers.get('User-Agent') matches '/firefox/i'")); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($routes, $context); + + $notMatchingRequest = Request::create('/foo', 'GET'); + $traces = $matcher->getTracesForRequest($notMatchingRequest); + $this->assertEquals("Condition \"request.headers.get('User-Agent') matches '/firefox/i'\" does not evaluate to \"true\"", $traces[0]['log']); + + $matchingRequest = Request::create('/foo', 'GET', array(), array(), array(), array('HTTP_USER_AGENT' => 'Firefox')); + $traces = $matcher->getTracesForRequest($matchingRequest); + $this->assertEquals('Route matches!', $traces[0]['log']); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php new file mode 100644 index 0000000..e8d31e2 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php @@ -0,0 +1,509 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class UrlMatcherTest extends TestCase +{ + public function testNoMethodSoAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo')); + + $matcher = $this->getUrlMatcher($coll); + $this->assertInternalType('array', $matcher->match('/foo')); + } + + public function testMethodNotAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('post'))); + + $matcher = $this->getUrlMatcher($coll); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST'), $e->getAllowedMethods()); + } + } + + public function testMethodNotAllowedOnRoot() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/', array(), array(), array(), '', array(), array('GET'))); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'POST')); + + try { + $matcher->match('/'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('GET'), $e->getAllowedMethods()); + } + } + + public function testHeadAllowedWhenRequirementContainsGet() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get'))); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'head')); + $this->assertInternalType('array', $matcher->match('/foo')); + } + + public function testMethodNotAllowedAggregatesAllowedMethods() + { + $coll = new RouteCollection(); + $coll->add('foo1', new Route('/foo', array(), array(), array(), '', array(), array('post'))); + $coll->add('foo2', new Route('/foo', array(), array(), array(), '', array(), array('put', 'delete'))); + + $matcher = $this->getUrlMatcher($coll); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST', 'PUT', 'DELETE'), $e->getAllowedMethods()); + } + } + + public function testMatch() + { + // test the patterns are matched and parameters are returned + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}')); + $matcher = $this->getUrlMatcher($collection); + try { + $matcher->match('/no-match'); + $this->fail(); + } catch (ResourceNotFoundException $e) { + } + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz'), $matcher->match('/foo/baz')); + + // test that defaults are merged + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}', array('def' => 'test'))); + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'def' => 'test'), $matcher->match('/foo/baz')); + + // test that route "method" is ignored if no method is given in the context + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get', 'head'))); + $matcher = $this->getUrlMatcher($collection); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route does not match with POST method context + $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'post')); + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + } + + // route does match with GET or HEAD method context + $matcher = $this->getUrlMatcher($collection); + $this->assertInternalType('array', $matcher->match('/foo')); + $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'head')); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route with an optional variable as the first segment + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}/foo', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/bar/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo/foo')); + + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/')); + + // route with only optional variables + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar'), array())); + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'bar' => 'bar'), $matcher->match('/')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'bar'), $matcher->match('/a')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'b'), $matcher->match('/a/b')); + } + + public function testMatchWithPrefixes() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/a'); + + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'foo', 'foo' => 'foo'), $matcher->match('/a/b/foo')); + } + + public function testMatchWithDynamicPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/{_locale}'); + + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_locale' => 'fr', '_route' => 'foo', 'foo' => 'foo'), $matcher->match('/fr/b/foo')); + } + + public function testMatchSpecialRouteName() + { + $collection = new RouteCollection(); + $collection->add('$péß^a|', new Route('/bar')); + + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testTrailingEncodedNewlineIsNotOverlooked() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $matcher = $this->getUrlMatcher($collection); + $matcher->match('/foo%0a'); + } + + public function testMatchNonAlpha() + { + $collection = new RouteCollection(); + $chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-'; + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '['.preg_quote($chars).']+'), array('utf8' => true))); + + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.rawurlencode($chars).'/bar')); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar')); + } + + public function testMatchWithDotMetacharacterInRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '.+'))); + + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'foo', 'foo' => "\n"), $matcher->match('/'.urlencode("\n").'/bar'), 'linefeed character is matched'); + } + + public function testMatchOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection->addCollection($collection1); + + $matcher = $this->getUrlMatcher($collection); + + $this->assertEquals(array('_route' => 'foo'), $matcher->match('/foo1')); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $this->assertEquals(array(), $matcher->match('/foo')); + } + + public function testMatchRegression() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/foo/bar/{foo}')); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar'), $matcher->match('/foo/bar/bar')); + + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{bar}')); + $matcher = $this->getUrlMatcher($collection); + try { + $matcher->match('/'); + $this->fail(); + } catch (ResourceNotFoundException $e) { + } + } + + public function testDefaultRequirementForOptionalVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array('page' => 'index', '_format' => 'html'))); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('page' => 'my-page', '_format' => 'xml', '_route' => 'test'), $matcher->match('/my-page.xml')); + } + + public function testMatchingIsEager() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{foo}-{bar}-', array(), array('foo' => '.+', 'bar' => '.+'))); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('foo' => 'text1-text2-text3', 'bar' => 'text4', '_route' => 'test'), $matcher->match('/text1-text2-text3-text4-')); + } + + public function testAdjacentVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => 'y|Y'))); + + $matcher = $this->getUrlMatcher($coll); + // 'w' eagerly matches as much as possible and the other variables match the remaining chars. + // This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement. + // Otherwise they would also consume '.xml' and _format would never match as it's an optional variable. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'Y', 'z' => 'Z', '_format' => 'xml', '_route' => 'test'), $matcher->match('/wwwwwxYZ.xml')); + // As 'y' has custom requirement and can only be of value 'y|Y', it will leave 'ZZZ' to variable z. + // So with carefully chosen requirements adjacent variables, can be useful. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'ZZZ', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxyZZZ')); + // z and _format are optional. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'default-z', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxy')); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/wxy.html'); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}', array('what' => 'All'))); + $matcher = $this->getUrlMatcher($coll); + + $this->assertEquals(array('what' => 'All', '_route' => 'test'), $matcher->match('/get')); + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSites')); + + // Usually the character in front of an optional parameter can be left out, e.g. with pattern '/get/{what}' just '/get' would match. + // But here the 't' in 'get' is not a separating character, so it makes no sense to match without it. + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/ge'); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}Suffix')); + $matcher = $this->getUrlMatcher($coll); + + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSitesSuffix')); + } + + public function testDefaultRequirementOfVariable() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = $this->getUrlMatcher($coll); + + $this->assertEquals(array('page' => 'index', '_format' => 'mobile.html', '_route' => 'test'), $matcher->match('/index.mobile.html')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = $this->getUrlMatcher($coll); + + $matcher->match('/index.sl/ash'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array(), array('_format' => 'html|xml'))); + $matcher = $this->getUrlMatcher($coll); + + $matcher->match('/do.t.html'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testSchemeRequirement() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https'))); + $matcher = $this->getUrlMatcher($coll); + $matcher->match('/foo'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testCondition() + { + $coll = new RouteCollection(); + $route = new Route('/foo'); + $route->setCondition('context.getMethod() == "POST"'); + $coll->add('foo', $route); + $matcher = $this->getUrlMatcher($coll); + $matcher->match('/foo'); + } + + public function testRequestCondition() + { + $coll = new RouteCollection(); + $route = new Route('/foo/{bar}'); + $route->setCondition('request.getBaseUrl() == "/sub/front.php" and request.getPathInfo() == "/foo/bar"'); + $coll->add('foo', $route); + $matcher = $this->getUrlMatcher($coll, new RequestContext('/sub/front.php')); + $this->assertEquals(array('bar' => 'bar', '_route' => 'foo'), $matcher->match('/foo/bar')); + } + + public function testDecodeOnce() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523')); + } + + public function testCannotRelyOnPrefix() + { + $coll = new RouteCollection(); + + $subColl = new RouteCollection(); + $subColl->add('bar', new Route('/bar')); + $subColl->addPrefix('/prefix'); + // overwrite the pattern, so the prefix is not valid anymore for this route in the collection + $subColl->get('bar')->setPath('/new'); + + $coll->addCollection($subColl); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('_route' => 'bar'), $matcher->match('/new')); + } + + public function testWithHost() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + } + + public function testWithHostOnRouteCollection() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net')); + $coll->setHost('{locale}.example.com'); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testWithOutHostHostDoesNotMatch() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'example.com')); + $matcher->match('/foo/bar'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testPathIsCaseSensitive() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/locale', array(), array('locale' => 'EN|FR|DE'))); + + $matcher = $this->getUrlMatcher($coll); + $matcher->match('/en'); + } + + public function testHostIsCaseInsensitive() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/', array(), array('locale' => 'EN|FR|DE'), array(), '{locale}.example.com')); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\NoConfigurationException + */ + public function testNoConfiguration() + { + $coll = new RouteCollection(); + + $matcher = $this->getUrlMatcher($coll); + $matcher->match('/'); + } + + public function testNestedCollections() + { + $coll = new RouteCollection(); + + $subColl = new RouteCollection(); + $subColl->add('a', new Route('/a')); + $subColl->add('b', new Route('/b')); + $subColl->add('c', new Route('/c')); + $subColl->addPrefix('/p'); + $coll->addCollection($subColl); + + $coll->add('baz', new Route('/{baz}')); + + $subColl = new RouteCollection(); + $subColl->add('buz', new Route('/buz')); + $subColl->addPrefix('/prefix'); + $coll->addCollection($subColl); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('_route' => 'a'), $matcher->match('/p/a')); + $this->assertEquals(array('_route' => 'baz', 'baz' => 'p'), $matcher->match('/p')); + $this->assertEquals(array('_route' => 'buz'), $matcher->match('/prefix/buz')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testSchemeAndMethodMismatch() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/', array(), array(), array(), null, array('https'), array('POST'))); + + $matcher = $this->getUrlMatcher($coll); + $matcher->match('/'); + } + + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + return new UrlMatcher($routes, $context ?: new RequestContext()); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/RequestContextTest.php b/assets/php/vendor/symfony/routing/Tests/RequestContextTest.php new file mode 100644 index 0000000..ffe29d1 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/RequestContextTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContext; + +class RequestContextTest extends TestCase +{ + public function testConstruct() + { + $requestContext = new RequestContext( + 'foo', + 'post', + 'foo.bar', + 'HTTPS', + 8080, + 444, + '/baz', + 'bar=foobar' + ); + + $this->assertEquals('foo', $requestContext->getBaseUrl()); + $this->assertEquals('POST', $requestContext->getMethod()); + $this->assertEquals('foo.bar', $requestContext->getHost()); + $this->assertEquals('https', $requestContext->getScheme()); + $this->assertSame(8080, $requestContext->getHttpPort()); + $this->assertSame(444, $requestContext->getHttpsPort()); + $this->assertEquals('/baz', $requestContext->getPathInfo()); + $this->assertEquals('bar=foobar', $requestContext->getQueryString()); + } + + public function testFromRequest() + { + $request = Request::create('https://test.com:444/foo?bar=baz'); + $requestContext = new RequestContext(); + $requestContext->setHttpPort(123); + $requestContext->fromRequest($request); + + $this->assertEquals('', $requestContext->getBaseUrl()); + $this->assertEquals('GET', $requestContext->getMethod()); + $this->assertEquals('test.com', $requestContext->getHost()); + $this->assertEquals('https', $requestContext->getScheme()); + $this->assertEquals('/foo', $requestContext->getPathInfo()); + $this->assertEquals('bar=baz', $requestContext->getQueryString()); + $this->assertSame(123, $requestContext->getHttpPort()); + $this->assertSame(444, $requestContext->getHttpsPort()); + + $request = Request::create('http://test.com:8080/foo?bar=baz'); + $requestContext = new RequestContext(); + $requestContext->setHttpsPort(567); + $requestContext->fromRequest($request); + + $this->assertSame(8080, $requestContext->getHttpPort()); + $this->assertSame(567, $requestContext->getHttpsPort()); + } + + public function testGetParameters() + { + $requestContext = new RequestContext(); + $this->assertEquals(array(), $requestContext->getParameters()); + + $requestContext->setParameters(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $requestContext->getParameters()); + } + + public function testHasParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameters(array('foo' => 'bar')); + + $this->assertTrue($requestContext->hasParameter('foo')); + $this->assertFalse($requestContext->hasParameter('baz')); + } + + public function testGetParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameters(array('foo' => 'bar')); + + $this->assertEquals('bar', $requestContext->getParameter('foo')); + $this->assertNull($requestContext->getParameter('baz')); + } + + public function testSetParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameter('foo', 'bar'); + + $this->assertEquals('bar', $requestContext->getParameter('foo')); + } + + public function testMethod() + { + $requestContext = new RequestContext(); + $requestContext->setMethod('post'); + + $this->assertSame('POST', $requestContext->getMethod()); + } + + public function testScheme() + { + $requestContext = new RequestContext(); + $requestContext->setScheme('HTTPS'); + + $this->assertSame('https', $requestContext->getScheme()); + } + + public function testHost() + { + $requestContext = new RequestContext(); + $requestContext->setHost('eXampLe.com'); + + $this->assertSame('example.com', $requestContext->getHost()); + } + + public function testQueryString() + { + $requestContext = new RequestContext(); + $requestContext->setQueryString(null); + + $this->assertSame('', $requestContext->getQueryString()); + } + + public function testPort() + { + $requestContext = new RequestContext(); + $requestContext->setHttpPort('123'); + $requestContext->setHttpsPort('456'); + + $this->assertSame(123, $requestContext->getHttpPort()); + $this->assertSame(456, $requestContext->getHttpsPort()); + } + + public function testFluentInterface() + { + $requestContext = new RequestContext(); + + $this->assertSame($requestContext, $requestContext->setBaseUrl('/app.php')); + $this->assertSame($requestContext, $requestContext->setPathInfo('/index')); + $this->assertSame($requestContext, $requestContext->setMethod('POST')); + $this->assertSame($requestContext, $requestContext->setScheme('https')); + $this->assertSame($requestContext, $requestContext->setHost('example.com')); + $this->assertSame($requestContext, $requestContext->setQueryString('foo=bar')); + $this->assertSame($requestContext, $requestContext->setHttpPort(80)); + $this->assertSame($requestContext, $requestContext->setHttpsPort(443)); + $this->assertSame($requestContext, $requestContext->setParameters(array())); + $this->assertSame($requestContext, $requestContext->setParameter('foo', 'bar')); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php b/assets/php/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php new file mode 100644 index 0000000..76a042d --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php @@ -0,0 +1,364 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouteCollectionBuilder; + +class RouteCollectionBuilderTest extends TestCase +{ + public function testImport() + { + $resolvedLoader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock(); + $resolver->expects($this->once()) + ->method('resolve') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($resolvedLoader)); + + $originalRoute = new Route('/foo/path'); + $expectedCollection = new RouteCollection(); + $expectedCollection->add('one_test_route', $originalRoute); + $expectedCollection->addResource(new FileResource(__DIR__.'/Fixtures/file_resource.yml')); + + $resolvedLoader + ->expects($this->once()) + ->method('load') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($expectedCollection)); + + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $loader->expects($this->any()) + ->method('getResolver') + ->will($this->returnValue($resolver)); + + // import the file! + $routes = new RouteCollectionBuilder($loader); + $importedRoutes = $routes->import('admin_routing.yml', '/', 'yaml'); + + // we should get back a RouteCollectionBuilder + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $importedRoutes); + + // get the collection back so we can look at it + $addedCollection = $importedRoutes->build(); + $route = $addedCollection->get('one_test_route'); + $this->assertSame($originalRoute, $route); + // should return file_resource.yml, which is in the original collection + $this->assertCount(1, $addedCollection->getResources()); + + // make sure the routes were imported into the top-level builder + $routeCollection = $routes->build(); + $this->assertCount(1, $routes->build()); + $this->assertCount(1, $routeCollection->getResources()); + } + + public function testImportAddResources() + { + $routeCollectionBuilder = new RouteCollectionBuilder(new YamlFileLoader(new FileLocator(array(__DIR__.'/Fixtures/')))); + $routeCollectionBuilder->import('file_resource.yml'); + $routeCollection = $routeCollectionBuilder->build(); + + $this->assertCount(1, $routeCollection->getResources()); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testImportWithoutLoaderThrowsException() + { + $collectionBuilder = new RouteCollectionBuilder(); + $collectionBuilder->import('routing.yml'); + } + + public function testAdd() + { + $collectionBuilder = new RouteCollectionBuilder(); + + $addedRoute = $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout'); + $addedRoute2 = $collectionBuilder->add('/blogs', 'AppBundle:Blog:list', 'blog_list'); + $this->assertInstanceOf('Symfony\Component\Routing\Route', $addedRoute); + $this->assertEquals('AppBundle:Order:checkout', $addedRoute->getDefault('_controller')); + + $finalCollection = $collectionBuilder->build(); + $this->assertSame($addedRoute2, $finalCollection->get('blog_list')); + } + + public function testFlushOrdering() + { + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route1', new Route('/imported/foo1')); + $importedCollection->add('imported_route2', new Route('/imported/foo2')); + + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); + $loader + ->expects($this->once()) + ->method('load') + ->will($this->returnValue($importedCollection)); + + $routes = new RouteCollectionBuilder($loader); + + // 1) Add a route + $routes->add('/checkout', 'AppBundle:Order:checkout', 'checkout_route'); + // 2) Import from a file + $routes->mount('/', $routes->import('admin_routing.yml')); + // 3) Add another route + $routes->add('/', 'AppBundle:Default:homepage', 'homepage'); + // 4) Add another route + $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + + // set a default value + $routes->setDefault('_locale', 'fr'); + + $actualCollection = $routes->build(); + + $this->assertCount(5, $actualCollection); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'checkout_route', + 'imported_route1', + 'imported_route2', + 'homepage', + 'admin_dashboard', + ), $actualRouteNames); + + // make sure the defaults were set + $checkoutRoute = $actualCollection->get('checkout_route'); + $defaults = $checkoutRoute->getDefaults(); + $this->assertArrayHasKey('_locale', $defaults); + $this->assertEquals('fr', $defaults['_locale']); + } + + public function testFlushSetsRouteNames() + { + $collectionBuilder = new RouteCollectionBuilder(); + + // add a "named" route + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + // add an unnamed route + $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') + ->setMethods(array('GET')); + + // integer route names are allowed - they don't confuse things + $collectionBuilder->add('/products', 'AppBundle:Product:list', 100); + + $actualCollection = $collectionBuilder->build(); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'admin_dashboard', + 'GET_blogs', + '100', + ), $actualRouteNames); + } + + public function testFlushSetsDetailsOnChildrenRoutes() + { + $routes = new RouteCollectionBuilder(); + + $routes->add('/blogs/{page}', 'listAction', 'blog_list') + // unique things for the route + ->setDefault('page', 1) + ->setRequirement('id', '\d+') + ->setOption('expose', true) + // things that the collection will try to override (but won't) + ->setDefault('_format', 'html') + ->setRequirement('_format', 'json|xml') + ->setOption('fooBar', true) + ->setHost('example.com') + ->setCondition('request.isSecure()') + ->setSchemes(array('https')) + ->setMethods(array('POST')); + + // a simple route, nothing added to it + $routes->add('/blogs/{id}', 'editAction', 'blog_edit'); + + // configure the collection itself + $routes + // things that will not override the child route + ->setDefault('_format', 'json') + ->setRequirement('_format', 'xml') + ->setOption('fooBar', false) + ->setHost('symfony.com') + ->setCondition('request.query.get("page")==1') + // some unique things that should be set on the child + ->setDefault('_locale', 'fr') + ->setRequirement('_locale', 'fr|en') + ->setOption('niceRoute', true) + ->setSchemes(array('http')) + ->setMethods(array('GET', 'POST')); + + $collection = $routes->build(); + $actualListRoute = $collection->get('blog_list'); + + $this->assertEquals(1, $actualListRoute->getDefault('page')); + $this->assertEquals('\d+', $actualListRoute->getRequirement('id')); + $this->assertTrue($actualListRoute->getOption('expose')); + // none of these should be overridden + $this->assertEquals('html', $actualListRoute->getDefault('_format')); + $this->assertEquals('json|xml', $actualListRoute->getRequirement('_format')); + $this->assertTrue($actualListRoute->getOption('fooBar')); + $this->assertEquals('example.com', $actualListRoute->getHost()); + $this->assertEquals('request.isSecure()', $actualListRoute->getCondition()); + $this->assertEquals(array('https'), $actualListRoute->getSchemes()); + $this->assertEquals(array('POST'), $actualListRoute->getMethods()); + // inherited from the main collection + $this->assertEquals('fr', $actualListRoute->getDefault('_locale')); + $this->assertEquals('fr|en', $actualListRoute->getRequirement('_locale')); + $this->assertTrue($actualListRoute->getOption('niceRoute')); + + $actualEditRoute = $collection->get('blog_edit'); + // inherited from the collection + $this->assertEquals('symfony.com', $actualEditRoute->getHost()); + $this->assertEquals('request.query.get("page")==1', $actualEditRoute->getCondition()); + $this->assertEquals(array('http'), $actualEditRoute->getSchemes()); + $this->assertEquals(array('GET', 'POST'), $actualEditRoute->getMethods()); + } + + /** + * @dataProvider providePrefixTests + */ + public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedPath) + { + $routes = new RouteCollectionBuilder(); + + $routes->add($routePath, 'someController', 'test_route'); + + $outerRoutes = new RouteCollectionBuilder(); + $outerRoutes->mount($collectionPrefix, $routes); + + $collection = $outerRoutes->build(); + + $this->assertEquals($expectedPath, $collection->get('test_route')->getPath()); + } + + public function providePrefixTests() + { + $tests = array(); + // empty prefix is of course ok + $tests[] = array('', '/foo', '/foo'); + // normal prefix - does not matter if it's a wildcard + $tests[] = array('/{admin}', '/foo', '/{admin}/foo'); + // shows that a prefix will always be given the starting slash + $tests[] = array('0', '/foo', '/0/foo'); + + // spaces are ok, and double slahses at the end are cleaned + $tests[] = array('/ /', '/foo', '/ /foo'); + + return $tests; + } + + public function testFlushSetsPrefixedWithMultipleLevels() + { + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $routes = new RouteCollectionBuilder($loader); + + $routes->add('homepage', 'MainController::homepageAction', 'homepage'); + + $adminRoutes = $routes->createBuilder(); + $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard'); + + // embedded collection under /admin + $adminBlogRoutes = $routes->createBuilder(); + $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new'); + // mount into admin, but before the parent collection has been mounted + $adminRoutes->mount('/blog', $adminBlogRoutes); + + // now mount the /admin routes, above should all still be /blog/admin + $routes->mount('/admin', $adminRoutes); + // add a route after mounting + $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users'); + + // add another sub-collection after the mount + $otherAdminRoutes = $routes->createBuilder(); + $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales'); + $adminRoutes->mount('/stats', $otherAdminRoutes); + + // add a normal collection and see that it is also prefixed + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route', new Route('/foo')); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); + $loader + ->expects($this->any()) + ->method('load') + ->will($this->returnValue($importedCollection)); + // import this from the /admin route builder + $adminRoutes->import('admin.yml', '/imported'); + + $collection = $routes->build(); + $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); + $this->assertEquals('/admin/users', $collection->get('admin_users')->getPath(), 'Routes after mounting have the prefix'); + $this->assertEquals('/admin/blog/new', $collection->get('admin_blog_new')->getPath(), 'Sub-collections receive prefix even if mounted before parent prefix'); + $this->assertEquals('/admin/stats/sales', $collection->get('admin_stats_sales')->getPath(), 'Sub-collections receive prefix if mounted after parent prefix'); + $this->assertEquals('/admin/imported/foo', $collection->get('imported_route')->getPath(), 'Normal RouteCollections are also prefixed properly'); + } + + public function testAutomaticRouteNamesDoNotConflict() + { + $routes = new RouteCollectionBuilder(); + + $adminRoutes = $routes->createBuilder(); + // route 1 + $adminRoutes->add('/dashboard', ''); + + $accountRoutes = $routes->createBuilder(); + // route 2 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('GET')); + // route 3 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('POST')); + + $routes->mount('/admin', $adminRoutes); + $routes->mount('/account', $accountRoutes); + + $collection = $routes->build(); + // there are 2 routes (i.e. with non-conflicting names) + $this->assertCount(3, $collection->all()); + } + + public function testAddsThePrefixOnlyOnceWhenLoadingMultipleCollections() + { + $firstCollection = new RouteCollection(); + $firstCollection->add('a', new Route('/a')); + + $secondCollection = new RouteCollection(); + $secondCollection->add('b', new Route('/b')); + + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); + $loader + ->expects($this->any()) + ->method('load') + ->will($this->returnValue(array($firstCollection, $secondCollection))); + + $routeCollectionBuilder = new RouteCollectionBuilder($loader); + $routeCollectionBuilder->import('/directory/recurse/*', '/other/', 'glob'); + $routes = $routeCollectionBuilder->build()->all(); + + $this->assertCount(2, $routes); + $this->assertEquals('/other/a', $routes['a']->getPath()); + $this->assertEquals('/other/b', $routes['b']->getPath()); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/RouteCollectionTest.php b/assets/php/vendor/symfony/routing/Tests/RouteCollectionTest.php new file mode 100644 index 0000000..83457ff --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/RouteCollectionTest.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; + +class RouteCollectionTest extends TestCase +{ + public function testRoute() + { + $collection = new RouteCollection(); + $route = new Route('/foo'); + $collection->add('foo', $route); + $this->assertEquals(array('foo' => $route), $collection->all(), '->add() adds a route'); + $this->assertEquals($route, $collection->get('foo'), '->get() returns a route by name'); + $this->assertNull($collection->get('bar'), '->get() returns null if a route does not exist'); + } + + public function testOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + $collection->add('foo', new Route('/foo1')); + + $this->assertEquals('/foo1', $collection->get('foo')->getPath()); + } + + public function testDeepOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection2 = new RouteCollection(); + $collection2->add('foo', new Route('/foo2')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + + $this->assertEquals('/foo2', $collection1->get('foo')->getPath()); + $this->assertEquals('/foo2', $collection->get('foo')->getPath()); + } + + public function testIterator() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertInstanceOf('\ArrayIterator', $collection->getIterator()); + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'last' => $last), $collection->getIterator()->getArrayCopy()); + } + + public function testCount() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/bar')); + $collection->addCollection($collection1); + + $this->assertCount(2, $collection); + } + + public function testAddCollection() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + + $collection2 = new RouteCollection(); + $collection2->add('grandchild', $grandchild = new Route('/grandchild')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'grandchild' => $grandchild, 'last' => $last), $collection->all(), + '->addCollection() imports routes of another collection, overrides if necessary and adds them at the end'); + } + + public function testAddCollectionWithResources() + { + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection1 = new RouteCollection(); + $collection1->addResource($foo1 = new FileResource(__DIR__.'/Fixtures/foo1.xml')); + $collection->addCollection($collection1); + $this->assertEquals(array($foo, $foo1), $collection->getResources(), '->addCollection() merges resources'); + } + + public function testAddDefaultsAndRequirementsAndOptions() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}')); + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/{placeholder}', + array('_controller' => 'fixed', 'placeholder' => 'default'), array('placeholder' => '.+'), array('option' => 'value')) + ); + $collection->addCollection($collection1); + + $collection->addDefaults(array('placeholder' => 'new-default')); + $this->assertEquals(array('placeholder' => 'new-default'), $collection->get('foo')->getDefaults(), '->addDefaults() adds defaults to all routes'); + $this->assertEquals(array('_controller' => 'fixed', 'placeholder' => 'new-default'), $collection->get('bar')->getDefaults(), + '->addDefaults() adds defaults to all routes and overwrites existing ones'); + + $collection->addRequirements(array('placeholder' => '\d+')); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('foo')->getRequirements(), '->addRequirements() adds requirements to all routes'); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('bar')->getRequirements(), + '->addRequirements() adds requirements to all routes and overwrites existing ones'); + + $collection->addOptions(array('option' => 'new-value')); + $this->assertEquals( + array('option' => 'new-value', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), + $collection->get('bar')->getOptions(), '->addOptions() adds options to all routes and overwrites existing ones' + ); + } + + public function testAddPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + $collection2 = new RouteCollection(); + $collection2->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection2); + $collection->addPrefix(' / '); + $this->assertSame('/foo', $collection->get('foo')->getPath(), '->addPrefix() trims the prefix and a single slash has no effect'); + $collection->addPrefix('/{admin}', array('admin' => 'admin'), array('admin' => '\d+')); + $this->assertEquals('/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals('/{admin}/bar', $collection->get('bar')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('foo')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('bar')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('foo')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('bar')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $collection->addPrefix('0'); + $this->assertEquals('/0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() ensures a prefix must start with a slash and must not end with a slash'); + $collection->addPrefix('/ /'); + $this->assertSame('/ /0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() can handle spaces if desired'); + $this->assertSame('/ /0/{admin}/bar', $collection->get('bar')->getPath(), 'the route pattern of an added collection is in synch with the added prefix'); + } + + public function testAddPrefixOverridesDefaultsAndRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo.{_format}')); + $collection->add('bar', $bar = new Route('/bar.{_format}', array(), array('_format' => 'json'))); + $collection->addPrefix('/admin', array(), array('_format' => 'html')); + + $this->assertEquals('html', $collection->get('foo')->getRequirement('_format'), '->addPrefix() overrides existing requirements'); + $this->assertEquals('html', $collection->get('bar')->getRequirement('_format'), '->addPrefix() overrides existing requirements'); + } + + public function testResource() + { + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection->addResource($bar = new FileResource(__DIR__.'/Fixtures/bar.xml')); + $collection->addResource(new FileResource(__DIR__.'/Fixtures/foo.xml')); + + $this->assertEquals(array($foo, $bar), $collection->getResources(), + '->addResource() adds a resource and getResources() only returns unique ones by comparing the string representation'); + } + + public function testUniqueRouteWithGivenName() + { + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/old')); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('foo', $new = new Route('/new')); + + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + + $this->assertSame($new, $collection1->get('foo'), '->get() returns new route that overrode previous one'); + // size of 1 because collection1 contains /new but not /old anymore + $this->assertCount(1, $collection1->getIterator(), '->addCollection() removes previous routes when adding new routes with the same name'); + } + + public function testGet() + { + $collection1 = new RouteCollection(); + $collection1->add('a', $a = new Route('/a')); + $collection2 = new RouteCollection(); + $collection2->add('b', $b = new Route('/b')); + $collection1->addCollection($collection2); + $collection1->add('$péß^a|', $c = new Route('/special')); + + $this->assertSame($b, $collection1->get('b'), '->get() returns correct route in child collection'); + $this->assertSame($c, $collection1->get('$péß^a|'), '->get() can handle special characters'); + $this->assertNull($collection2->get('a'), '->get() does not return the route defined in parent collection'); + $this->assertNull($collection1->get('non-existent'), '->get() returns null when route does not exist'); + $this->assertNull($collection1->get(0), '->get() does not disclose internal child RouteCollection'); + } + + public function testRemove() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $collection->remove('foo'); + $this->assertSame(array('bar' => $bar, 'last' => $last), $collection->all(), '->remove() can remove a single route'); + $collection->remove(array('bar', 'last')); + $this->assertSame(array(), $collection->all(), '->remove() accepts an array and can remove multiple routes at once'); + } + + public function testSetHost() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setHost('{locale}.example.com'); + + $this->assertEquals('{locale}.example.com', $routea->getHost()); + $this->assertEquals('{locale}.example.com', $routeb->getHost()); + } + + public function testSetCondition() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net', array(), array(), 'context.getMethod() == "GET"'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setCondition('context.getMethod() == "POST"'); + + $this->assertEquals('context.getMethod() == "POST"', $routea->getCondition()); + $this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition()); + } + + public function testClone() + { + $collection = new RouteCollection(); + $collection->add('a', new Route('/a')); + $collection->add('b', new Route('/b', array('placeholder' => 'default'), array('placeholder' => '.+'))); + + $clonedCollection = clone $collection; + + $this->assertCount(2, $clonedCollection); + $this->assertEquals($collection->get('a'), $clonedCollection->get('a')); + $this->assertNotSame($collection->get('a'), $clonedCollection->get('a')); + $this->assertEquals($collection->get('b'), $clonedCollection->get('b')); + $this->assertNotSame($collection->get('b'), $clonedCollection->get('b')); + } + + public function testSetSchemes() + { + $collection = new RouteCollection(); + $routea = new Route('/a', array(), array(), array(), '', 'http'); + $routeb = new Route('/b'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setSchemes(array('http', 'https')); + + $this->assertEquals(array('http', 'https'), $routea->getSchemes()); + $this->assertEquals(array('http', 'https'), $routeb->getSchemes()); + } + + public function testSetMethods() + { + $collection = new RouteCollection(); + $routea = new Route('/a', array(), array(), array(), '', array(), array('GET', 'POST')); + $routeb = new Route('/b'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setMethods('PUT'); + + $this->assertEquals(array('PUT'), $routea->getMethods()); + $this->assertEquals(array('PUT'), $routeb->getMethods()); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/RouteCompilerTest.php b/assets/php/vendor/symfony/routing/Tests/RouteCompilerTest.php new file mode 100644 index 0000000..dc304e3 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/RouteCompilerTest.php @@ -0,0 +1,389 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCompiler; + +class RouteCompilerTest extends TestCase +{ + /** + * @dataProvider provideCompileData + */ + public function testCompile($name, $arguments, $prefix, $regex, $variables, $tokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + } + + public function provideCompileData() + { + return array( + array( + 'Static route', + array('/foo'), + '/foo', '#^/foo$#sD', array(), array( + array('text', '/foo'), + ), + ), + + array( + 'Route with a variable', + array('/foo/{bar}'), + '/foo', '#^/foo/(?P[^/]++)$#sD', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with a variable that has a default value', + array('/foo/{bar}', array('bar' => 'bar')), + '/foo', '#^/foo(?:/(?P[^/]++))?$#sD', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables', + array('/foo/{bar}/{foobar}'), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#sD', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables that have default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), + '/foo', '#^/foo(?:/(?P[^/]++)(?:/(?P[^/]++))?)?$#sD', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables but some of them have no default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar')), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#sD', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with an optional variable as the first segment', + array('/{bar}', array('bar' => 'bar')), + '', '#^/(?P[^/]++)?$#sD', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + ), + ), + + array( + 'Route with a requirement of 0', + array('/{bar}', array('bar' => null), array('bar' => '0')), + '', '#^/(?P0)?$#sD', array('bar'), array( + array('variable', '/', '0', 'bar'), + ), + ), + + array( + 'Route with an optional variable as the first segment with requirements', + array('/{bar}', array('bar' => 'bar'), array('bar' => '(foo|bar)')), + '', '#^/(?P(foo|bar))?$#sD', array('bar'), array( + array('variable', '/', '(foo|bar)', 'bar'), + ), + ), + + array( + 'Route with only optional variables', + array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')), + '', '#^/(?P[^/]++)?(?:/(?P[^/]++))?$#sD', array('foo', 'bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('variable', '/', '[^/]++', 'foo'), + ), + ), + + array( + 'Route with a variable in last position', + array('/foo-{bar}'), + '/foo-', '#^/foo\-(?P[^/]++)$#sD', array('bar'), array( + array('variable', '-', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with nested placeholders', + array('/{static{var}static}'), + '/{static', '#^/\{static(?P[^/]+)static\}$#sD', array('var'), array( + array('text', 'static}'), + array('variable', '', '[^/]+', 'var'), + array('text', '/{static'), + ), + ), + + array( + 'Route without separator between variables', + array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')), + '', '#^/(?P[^/\.]+)(?P[^/\.]+)(?P(y|Y))(?:(?P[^/\.]++)(?:\.(?P<_format>[^/]++))?)?$#sD', array('w', 'x', 'y', 'z', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '', '[^/\.]++', 'z'), + array('variable', '', '(y|Y)', 'y'), + array('variable', '', '[^/\.]+', 'x'), + array('variable', '/', '[^/\.]+', 'w'), + ), + ), + + array( + 'Route with a format', + array('/foo/{bar}.{_format}'), + '/foo', '#^/foo/(?P[^/\.]++)\.(?P<_format>[^/]++)$#sD', array('bar', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '/', '[^/\.]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Static non UTF-8 route', + array("/fo\xE9"), + "/fo\xE9", "#^/fo\xE9$#sD", array(), array( + array('text', "/fo\xE9"), + ), + ), + + array( + 'Route with an explicit UTF-8 requirement', + array('/{bar}', array('bar' => null), array('bar' => '.'), array('utf8' => true)), + '', '#^/(?P.)?$#sDu', array('bar'), array( + array('variable', '/', '.', 'bar', true), + ), + ), + ); + } + + /** + * @group legacy + * @dataProvider provideCompileImplicitUtf8Data + * @expectedDeprecation Using UTF-8 route %s without setting the "utf8" option is deprecated %s. + */ + public function testCompileImplicitUtf8Data($name, $arguments, $prefix, $regex, $variables, $tokens, $deprecationType) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + } + + public function provideCompileImplicitUtf8Data() + { + return array( + array( + 'Static UTF-8 route', + array('/foé'), + '/foé', '#^/foé$#sDu', array(), array( + array('text', '/foé'), + ), + 'patterns', + ), + + array( + 'Route with an implicit UTF-8 requirement', + array('/{bar}', array('bar' => null), array('bar' => 'é')), + '', '#^/(?Pé)?$#sDu', array('bar'), array( + array('variable', '/', 'é', 'bar', true), + ), + 'requirements', + ), + + array( + 'Route with a UTF-8 class requirement', + array('/{bar}', array('bar' => null), array('bar' => '\pM')), + '', '#^/(?P\pM)?$#sDu', array('bar'), array( + array('variable', '/', '\pM', 'bar', true), + ), + 'requirements', + ), + + array( + 'Route with a UTF-8 separator', + array('/foo/{bar}§{_format}', array(), array(), array('compiler_class' => Utf8RouteCompiler::class)), + '/foo', '#^/foo/(?P[^/§]++)§(?P<_format>[^/]++)$#sDu', array('bar', '_format'), array( + array('variable', '§', '[^/]++', '_format', true), + array('variable', '/', '[^/§]++', 'bar', true), + array('text', '/foo'), + ), + 'patterns', + ), + ); + } + + /** + * @expectedException \LogicException + */ + public function testRouteWithSameVariableTwice() + { + $route = new Route('/{name}/{name}'); + + $compiled = $route->compile(); + } + + /** + * @expectedException \LogicException + */ + public function testRouteCharsetMismatch() + { + $route = new Route("/\xE9/{bar}", array(), array('bar' => '.'), array('utf8' => true)); + + $compiled = $route->compile(); + } + + /** + * @expectedException \LogicException + */ + public function testRequirementCharsetMismatch() + { + $route = new Route('/foo/{bar}', array(), array('bar' => "\xE9"), array('utf8' => true)); + + $compiled = $route->compile(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRouteWithFragmentAsPathParameter() + { + $route = new Route('/{_fragment}'); + + $compiled = $route->compile(); + } + + /** + * @dataProvider getVariableNamesStartingWithADigit + * @expectedException \DomainException + */ + public function testRouteWithVariableNameStartingWithADigit($name) + { + $route = new Route('/{'.$name.'}'); + $route->compile(); + } + + public function getVariableNamesStartingWithADigit() + { + return array( + array('09'), + array('123'), + array('1e2'), + ); + } + + /** + * @dataProvider provideCompileWithHostData + */ + public function testCompileWithHost($name, $arguments, $prefix, $regex, $variables, $pathVariables, $tokens, $hostRegex, $hostVariables, $hostTokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($pathVariables, $compiled->getPathVariables(), $name.' (path variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + $this->assertEquals($hostRegex, str_replace(array("\n", ' '), '', $compiled->getHostRegex()), $name.' (host regex)'); + $this->assertEquals($hostVariables, $compiled->getHostVariables(), $name.' (host variables)'); + $this->assertEquals($hostTokens, $compiled->getHostTokens(), $name.' (host tokens)'); + } + + public function provideCompileWithHostData() + { + return array( + array( + 'Route with host pattern', + array('/hello', array(), array(), array(), 'www.example.com'), + '/hello', '#^/hello$#sD', array(), array(), array( + array('text', '/hello'), + ), + '#^www\.example\.com$#sDi', array(), array( + array('text', 'www.example.com'), + ), + ), + array( + 'Route with host pattern and some variables', + array('/hello/{name}', array(), array(), array(), 'www.example.{tld}'), + '/hello', '#^/hello/(?P[^/]++)$#sD', array('tld', 'name'), array('name'), array( + array('variable', '/', '[^/]++', 'name'), + array('text', '/hello'), + ), + '#^www\.example\.(?P[^\.]++)$#sDi', array('tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', 'www.example'), + ), + ), + array( + 'Route with variable at beginning of host', + array('/hello', array(), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#sD', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#sDi', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + array( + 'Route with host variables that has a default value', + array('/hello', array('locale' => 'a', 'tld' => 'b'), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#sD', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#sDi', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + ); + } + + /** + * @expectedException \DomainException + */ + public function testRouteWithTooLongVariableName() + { + $route = new Route(sprintf('/{%s}', str_repeat('a', RouteCompiler::VARIABLE_MAXIMUM_LENGTH + 1))); + $route->compile(); + } +} + +class Utf8RouteCompiler extends RouteCompiler +{ + const SEPARATORS = '/§'; +} diff --git a/assets/php/vendor/symfony/routing/Tests/RouteTest.php b/assets/php/vendor/symfony/routing/Tests/RouteTest.php new file mode 100644 index 0000000..c7af058 --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/RouteTest.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; + +class RouteTest extends TestCase +{ + public function testConstructor() + { + $route = new Route('/{foo}', array('foo' => 'bar'), array('foo' => '\d+'), array('foo' => 'bar'), '{locale}.example.com'); + $this->assertEquals('/{foo}', $route->getPath(), '__construct() takes a path as its first argument'); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '__construct() takes defaults as its second argument'); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '__construct() takes requirements as its third argument'); + $this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument'); + $this->assertEquals('{locale}.example.com', $route->getHost(), '__construct() takes a host pattern as its fifth argument'); + + $route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put'), 'context.getMethod() == "GET"'); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes schemes as its sixth argument and lowercases it'); + $this->assertEquals(array('POST', 'PUT'), $route->getMethods(), '__construct() takes methods as its seventh argument and uppercases it'); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition(), '__construct() takes a condition as its eight argument'); + + $route = new Route('/', array(), array(), array(), '', 'Https', 'Post'); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes a single scheme as its sixth argument'); + $this->assertEquals(array('POST'), $route->getMethods(), '__construct() takes a single method as its seventh argument'); + } + + public function testPath() + { + $route = new Route('/{foo}'); + $route->setPath('/{bar}'); + $this->assertEquals('/{bar}', $route->getPath(), '->setPath() sets the path'); + $route->setPath(''); + $this->assertEquals('/', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $route->setPath('bar'); + $this->assertEquals('/bar', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $this->assertEquals($route, $route->setPath(''), '->setPath() implements a fluent interface'); + $route->setPath('//path'); + $this->assertEquals('/path', $route->getPath(), '->setPath() does not allow two slashes "//" at the beginning of the path as it would be confused with a network path when generating the path from the route'); + } + + public function testOptions() + { + $route = new Route('/{foo}'); + $route->setOptions(array('foo' => 'bar')); + $this->assertEquals(array_merge(array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ), array('foo' => 'bar')), $route->getOptions(), '->setOptions() sets the options'); + $this->assertEquals($route, $route->setOptions(array()), '->setOptions() implements a fluent interface'); + + $route->setOptions(array('foo' => 'foo')); + $route->addOptions(array('bar' => 'bar')); + $this->assertEquals($route, $route->addOptions(array()), '->addOptions() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), $route->getOptions(), '->addDefaults() keep previous defaults'); + } + + public function testOption() + { + $route = new Route('/{foo}'); + $this->assertFalse($route->hasOption('foo'), '->hasOption() return false if option is not set'); + $this->assertEquals($route, $route->setOption('foo', 'bar'), '->setOption() implements a fluent interface'); + $this->assertEquals('bar', $route->getOption('foo'), '->setOption() sets the option'); + $this->assertTrue($route->hasOption('foo'), '->hasOption() return true if option is set'); + } + + public function testDefaults() + { + $route = new Route('/{foo}'); + $route->setDefaults(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '->setDefaults() sets the defaults'); + $this->assertEquals($route, $route->setDefaults(array()), '->setDefaults() implements a fluent interface'); + + $route->setDefault('foo', 'bar'); + $this->assertEquals('bar', $route->getDefault('foo'), '->setDefault() sets a default value'); + + $route->setDefault('foo2', 'bar2'); + $this->assertEquals('bar2', $route->getDefault('foo2'), '->getDefault() return the default value'); + $this->assertNull($route->getDefault('not_defined'), '->getDefault() return null if default value is not set'); + + $route->setDefault('_controller', $closure = function () { return 'Hello'; }); + $this->assertEquals($closure, $route->getDefault('_controller'), '->setDefault() sets a default value'); + + $route->setDefaults(array('foo' => 'foo')); + $route->addDefaults(array('bar' => 'bar')); + $this->assertEquals($route, $route->addDefaults(array()), '->addDefaults() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar'), $route->getDefaults(), '->addDefaults() keep previous defaults'); + } + + public function testRequirements() + { + $route = new Route('/{foo}'); + $route->setRequirements(array('foo' => '\d+')); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '->setRequirements() sets the requirements'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() returns a requirement'); + $this->assertNull($route->getRequirement('bar'), '->getRequirement() returns null if a requirement is not defined'); + $route->setRequirements(array('foo' => '^\d+$')); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() removes ^ and $ from the path'); + $this->assertEquals($route, $route->setRequirements(array()), '->setRequirements() implements a fluent interface'); + + $route->setRequirements(array('foo' => '\d+')); + $route->addRequirements(array('bar' => '\d+')); + $this->assertEquals($route, $route->addRequirements(array()), '->addRequirements() implements a fluent interface'); + $this->assertEquals(array('foo' => '\d+', 'bar' => '\d+'), $route->getRequirements(), '->addRequirement() keep previous requirements'); + } + + public function testRequirement() + { + $route = new Route('/{foo}'); + $this->assertFalse($route->hasRequirement('foo'), '->hasRequirement() return false if requirement is not set'); + $route->setRequirement('foo', '^\d+$'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->setRequirement() removes ^ and $ from the path'); + $this->assertTrue($route->hasRequirement('foo'), '->hasRequirement() return true if requirement is set'); + } + + /** + * @dataProvider getInvalidRequirements + * @expectedException \InvalidArgumentException + */ + public function testSetInvalidRequirement($req) + { + $route = new Route('/{foo}'); + $route->setRequirement('foo', $req); + } + + public function getInvalidRequirements() + { + return array( + array(''), + array(array()), + array('^$'), + array('^'), + array('$'), + ); + } + + public function testHost() + { + $route = new Route('/'); + $route->setHost('{locale}.example.net'); + $this->assertEquals('{locale}.example.net', $route->getHost(), '->setHost() sets the host pattern'); + } + + public function testScheme() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getSchemes(), 'schemes is initialized with array()'); + $this->assertFalse($route->hasScheme('http')); + $route->setSchemes('hTTp'); + $this->assertEquals(array('http'), $route->getSchemes(), '->setSchemes() accepts a single scheme string and lowercases it'); + $this->assertTrue($route->hasScheme('htTp')); + $this->assertFalse($route->hasScheme('httpS')); + $route->setSchemes(array('HttpS', 'hTTp')); + $this->assertEquals(array('https', 'http'), $route->getSchemes(), '->setSchemes() accepts an array of schemes and lowercases them'); + $this->assertTrue($route->hasScheme('htTp')); + $this->assertTrue($route->hasScheme('httpS')); + } + + public function testMethod() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getMethods(), 'methods is initialized with array()'); + $route->setMethods('gEt'); + $this->assertEquals(array('GET'), $route->getMethods(), '->setMethods() accepts a single method string and uppercases it'); + $route->setMethods(array('gEt', 'PosT')); + $this->assertEquals(array('GET', 'POST'), $route->getMethods(), '->setMethods() accepts an array of methods and uppercases them'); + } + + public function testCondition() + { + $route = new Route('/'); + $this->assertSame('', $route->getCondition()); + $route->setCondition('context.getMethod() == "GET"'); + $this->assertSame('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testCompile() + { + $route = new Route('/{foo}'); + $this->assertInstanceOf('Symfony\Component\Routing\CompiledRoute', $compiled = $route->compile(), '->compile() returns a compiled route'); + $this->assertSame($compiled, $route->compile(), '->compile() only compiled the route once if unchanged'); + $route->setRequirement('foo', '.*'); + $this->assertNotSame($compiled, $route->compile(), '->compile() recompiles if the route was modified'); + } + + public function testSerialize() + { + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + + $serialized = serialize($route); + $unserialized = unserialize($serialized); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } + + /** + * Tests that the compiled version is also serialized to prevent the overhead + * of compiling it again after unserialize. + */ + public function testSerializeWhenCompiled() + { + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $serialized = serialize($route); + $unserialized = unserialize($serialized); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } + + /** + * Tests that unserialization does not fail when the compiled Route is of a + * class other than CompiledRoute, such as a subclass of it. + */ + public function testSerializeWhenCompiledWithClass() + { + $route = new Route('/', array(), array(), array('compiler_class' => '\Symfony\Component\Routing\Tests\Fixtures\CustomRouteCompiler')); + $this->assertInstanceOf('\Symfony\Component\Routing\Tests\Fixtures\CustomCompiledRoute', $route->compile(), '->compile() returned a proper route'); + + $serialized = serialize($route); + try { + $unserialized = unserialize($serialized); + $this->assertInstanceOf('\Symfony\Component\Routing\Tests\Fixtures\CustomCompiledRoute', $unserialized->compile(), 'the unserialized route compiled successfully'); + } catch (\Exception $e) { + $this->fail('unserializing a route which uses a custom compiled route class'); + } + } + + /** + * Tests that the serialized representation of a route in one symfony version + * also works in later symfony versions, i.e. the unserialized route is in the + * same state as another, semantically equivalent, route. + */ + public function testSerializedRepresentationKeepsWorking() + { + $serialized = 'C:31:"Symfony\Component\Routing\Route":936:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":571:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:31:"#^/prefix(?:/(?P\d+))?$#sD";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:40:"#^(?P[^\.]++)\.example\.net$#sDi";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}'; + $unserialized = unserialize($serialized); + + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } +} diff --git a/assets/php/vendor/symfony/routing/Tests/RouterTest.php b/assets/php/vendor/symfony/routing/Tests/RouterTest.php new file mode 100644 index 0000000..3e3d43f --- /dev/null +++ b/assets/php/vendor/symfony/routing/Tests/RouterTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Router; +use Symfony\Component\HttpFoundation\Request; + +class RouterTest extends TestCase +{ + private $router = null; + + private $loader = null; + + protected function setUp() + { + $this->loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $this->router = new Router($this->loader, 'routing.yml'); + } + + public function testSetOptionsWithSupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'debug' => true, + 'resource_type' => 'ResourceType', + )); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + $this->assertTrue($this->router->getOption('debug')); + $this->assertSame('ResourceType', $this->router->getOption('resource_type')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the following options: "option_foo", "option_bar" + */ + public function testSetOptionsWithUnsupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'option_foo' => true, + 'option_bar' => 'baz', + 'resource_type' => 'ResourceType', + )); + } + + public function testSetOptionWithSupportedOption() + { + $this->router->setOption('cache_dir', './cache'); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testSetOptionWithUnsupportedOption() + { + $this->router->setOption('option_foo', true); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testGetOptionWithUnsupportedOption() + { + $this->router->getOption('option_foo', true); + } + + public function testThatRouteCollectionIsLoaded() + { + $this->router->setOption('resource_type', 'ResourceType'); + + $routeCollection = new RouteCollection(); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', 'ResourceType') + ->will($this->returnValue($routeCollection)); + + $this->assertSame($routeCollection, $this->router->getRouteCollection()); + } + + /** + * @dataProvider provideMatcherOptionsPreventingCaching + */ + public function testMatcherIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue(new RouteCollection())); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Matcher\\UrlMatcher', $this->router->getMatcher()); + } + + public function provideMatcherOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('matcher_cache_class'), + ); + } + + /** + * @dataProvider provideGeneratorOptionsPreventingCaching + */ + public function testGeneratorIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue(new RouteCollection())); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Generator\\UrlGenerator', $this->router->getGenerator()); + } + + public function provideGeneratorOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('generator_cache_class'), + ); + } + + public function testMatchRequestWithUrlMatcherInterface() + { + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock(); + $matcher->expects($this->once())->method('match'); + + $p = new \ReflectionProperty($this->router, 'matcher'); + $p->setAccessible(true); + $p->setValue($this->router, $matcher); + + $this->router->matchRequest(Request::create('/')); + } + + public function testMatchRequestWithRequestMatcherInterface() + { + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $matcher->expects($this->once())->method('matchRequest'); + + $p = new \ReflectionProperty($this->router, 'matcher'); + $p->setAccessible(true); + $p->setValue($this->router, $matcher); + + $this->router->matchRequest(Request::create('/')); + } +} diff --git a/assets/php/vendor/symfony/routing/composer.json b/assets/php/vendor/symfony/routing/composer.json new file mode 100644 index 0000000..4d820cc --- /dev/null +++ b/assets/php/vendor/symfony/routing/composer.json @@ -0,0 +1,56 @@ +{ + "name": "symfony/routing", + "type": "library", + "description": "Symfony Routing Component", + "keywords": ["routing", "router", "URL", "URI"], + "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" + }, + "require-dev": { + "symfony/config": "^3.3.1|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.4" + }, + "suggest": { + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/yaml": "For using the YAML loader", + "symfony/expression-language": "For using expression matching", + "doctrine/annotations": "For using the annotation loader", + "symfony/dependency-injection": "For loading routes from a service" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Routing\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + } +} diff --git a/assets/php/vendor/symfony/routing/phpunit.xml.dist b/assets/php/vendor/symfony/routing/phpunit.xml.dist new file mode 100644 index 0000000..bcc0959 --- /dev/null +++ b/assets/php/vendor/symfony/routing/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + -- cgit v1.2.3