├── .gitignore ├── classes └── Deref │ ├── Exceptions │ ├── DerefException.php │ ├── InvalidUrlException.php │ ├── CommunicationException.php │ └── TooManyRedirectsException.php │ └── Deref.php ├── web ├── js │ ├── app.js │ ├── services.js │ └── controllers.js ├── css │ └── app.css └── index.php ├── composer.json ├── bootstrap.php ├── LICENSE ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /web/components/ 3 | /logs/ 4 | -------------------------------------------------------------------------------- /classes/Deref/Exceptions/DerefException.php: -------------------------------------------------------------------------------- 1 | ').text(text).html() 8 | } -------------------------------------------------------------------------------- /classes/Deref/Exceptions/TooManyRedirectsException.php: -------------------------------------------------------------------------------- 1 | =7.0.0", 6 | "ext-curl": "*", 7 | "slim/slim": "^3.10", 8 | "symfony/dom-crawler": "^3.4", 9 | "symfony/css-selector": "^3.4", 10 | "monolog/monolog": "^1.24" 11 | }, 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Kevin Boyd", 16 | "email": "kevin.boyd@gmail.com" 17 | } 18 | ], 19 | "autoload": { 20 | "psr-4": { 21 | "Deref\\": "classes/Deref" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bootstrap.php: -------------------------------------------------------------------------------- 1 | getContainer(); 7 | 8 | $container['debug'] = false; 9 | $container['displayErrorDetails'] = false; 10 | 11 | if (file_exists(__DIR__ . '/config.php')) { 12 | $config = include __DIR__ . '/config.php'; 13 | } 14 | 15 | $container['deref.config'] = is_array($config) ? $config : []; 16 | 17 | $container['logger'] = function ($c) { 18 | $logger = new \Monolog\Logger('my_logger'); 19 | $file_handler = new \Monolog\Handler\StreamHandler(__DIR__ . '/logs/app.log'); 20 | $logger->pushHandler($file_handler); 21 | 22 | return $logger; 23 | }; 24 | 25 | // Deref application service 26 | $container['deref'] = function ($c) { 27 | $deref = new \Deref\Deref(); 28 | 29 | $logger = $c['logger']; 30 | 31 | $deref->setLogger($logger); 32 | 33 | return $deref; 34 | }; 35 | 36 | return $app; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Kevin Boyd 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /web/js/controllers.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('DerefApp.controllers', []); 2 | 3 | app.controller('derefController', function ($scope, derefService) { 4 | $scope.derefUrl = ''; 5 | $scope.derefResponse = []; 6 | 7 | $scope.submitForm = function() { 8 | 9 | //Remove and hide the dynamic parts of the page before each submit 10 | $('div#error-alert').remove(); 11 | $('.results-box').hide(); 12 | 13 | // ensure provided URLs are HTTP:// or HTTPS:// 14 | if ($scope.derefUrl.trim().length === 0) { 15 | $scope.formError('Please specify a URL.'); 16 | return false; 17 | } 18 | 19 | derefService.derefUrl($scope.derefUrl).success(function (response) { 20 | $('.results-box').show(); 21 | $scope.derefResponse = response; 22 | }).error(function (error, errorCode) { 23 | $scope.formError(error.error, errorCode); 24 | }); 25 | }; 26 | 27 | $scope.formError = function(message, code) { 28 | $('input#derefUrl').before('' 36 | ); 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | \*DEREF 2 | ======= 3 | 4 | Ever come across a suspicious short URL and wanted to know where it **really** goes? 5 | 6 | **\*Deref** (pronounced "De-Ref") is a quick experiment in learning [AngularJS][angular] with a [SlimPHP][slim] backend. 7 | 8 | It uses cURL to follow the redirect chain on pasted URLs, logging all the hops 9 | and showing the final result using Angular and [Twitter Bootstrap][bootstrap]. 10 | 11 | To try it out, clone the source and use [Composer][composer] to install vendors: 12 | 13 | $> composer install 14 | 15 | Or, check it out online at [https://deref.link/][deref] 16 | 17 | Usage 18 | ----- 19 | 20 | Once the vendors are installed, you can set up a virtual host using apache or nginx, 21 | using a standard rewrite rule to send traffic to ```web/index.php```. 22 | 23 | Another option is to use the internal PHP webserver: 24 | 25 | $> php -S localhost:8080 -t web/ 26 | 27 | This will start the server on port 8080, which you can then visit in your web browser 28 | to try out the service. 29 | 30 | Configuration 31 | ------------- 32 | 33 | Creating a config.php file is optional; currently, it's only needed if you want to specify analytics code. 34 | 35 | Here's what a config.php file should look like: 36 | 37 | "... code goes here ...", 41 | ]; 42 | 43 | Author 44 | ------ 45 | 46 | [Kevin Boyd][@beryllium9] created **\*Deref** as part of [an AngularJS blog post][blogpost] on [whateverthing.com][whateverthing]. 47 | 48 | **\*Deref** is released under the MIT open-source license. 49 | 50 | [angular]: https://angularjs.org/ 51 | [silex]: http://silex.sensiolabs.org/ 52 | [bootstrap]: http://getbootstrap.com/ 53 | [composer]: https://getcomposer.org/ 54 | [deref]: https://deref.link/ 55 | [whateverthing]: https://whateverthing.com/ 56 | [blogpost]: https://whateverthing.com/blog/2014/12/07/angularjs-and-silex/ 57 | [@beryllium9]: https://twitter.com/beryllium9 58 | [slim]: https://www.slimframework.com/ -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | getContainer(); 5 | 6 | use Slim\Http\Request; 7 | use Slim\Http\Response; 8 | use Deref\Exceptions\DerefException; 9 | 10 | // Default (root) route - sets up the Angular controllers and defines the site appearance. 11 | $app->get('/', function(Request $request) use ($container) { 12 | /** @var \Psr\Log\LoggerInterface $logger */ 13 | $logger = $container['logger']; 14 | $logger->info('pageload', [$request->getServerParams()]); 15 | 16 | $config = $container['deref.config']; 17 | $analytics = $config['analytics'] ?? ''; 18 | $output = << 20 | 21 | Deref 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 |
31 |

Deref.link

32 |

Ever come across a suspicious short URL and wanted to know where it really goes?

33 |

Paste it here and find out!

34 |
35 | 36 | 37 | 38 |
39 |
40 |
41 |

Sample URLs

42 |

Want to try some samples? We've got you covered. Copy and paste one of these to try it out:

43 |
    44 |
  • http://t.co/GsOQGWW7D4
  • 45 |
  • http://kevinboyd.ca
  • 46 |
47 |
48 |
49 |
50 | 51 |
52 |
53 |

Result

54 |
55 |
56 |

This URL has {{ derefResponse.route_log.length - 1 }} redirect hop(s) to the final destination.

57 |
58 |
    59 |
  • Domain: {{ derefResponse.final_domain }}
  • 60 |
  • Final URL: {{ derefResponse.final_url }}
  • 61 |
  • Redirect Log: 62 |
      63 |
    • {{ \$index }} - {{route}}
    • 65 |
    66 |
  • 67 |
68 |
69 | 70 |
71 |
Quick API Reference
72 |
73 |
POST /deref
74 |
curl -d"url=google.com" https://deref.link/deref
75 | Or: 76 |
curl -H "Content-Type: application/json" \
 77 |      -d'{"url": "google.com"}' \
 78 |      https://deref.link/deref
79 |
Parameters:
80 | 81 | 82 | 83 | 84 | 86 | 87 | 88 |
urlThe URL-encoded link to dereference. HTTP or HTTPS.
85 | Leading "http://" can be omitted, but not for HTTPS.
89 |
Returns JSON: (return value reformatted for readability)
90 |
{
 91 |     "start_url":    "google.com",
 92 |     "final_url":    "http://www.google.com/",
 93 |     "final_domain": "www.google.com",
 94 |     "route_log": [
 95 |         "http://google.com",
 96 |         "http://www.google.com/"
 97 |     ]
 98 | }
99 |
100 |
101 |
102 |
103 |

a whateverthing project

104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | Fork me on GitHub 112 | 113 | $analytics 114 | 115 | 116 | 117 | END; 118 | 119 | return $output; 120 | }); 121 | 122 | // Deref route - accepts a URL parameter and responds with the redirect log in JSON 123 | $app->post('/deref', function(Request $request, Response $response) use ($container) { 124 | /** @var \Psr\Log\LoggerInterface $logger */ 125 | $logger = $container['logger']; 126 | 127 | /** @var \Deref\Deref $deref */ 128 | $deref = $container['deref']; 129 | 130 | $body = $request->getParsedBody(); 131 | $url = $body['url'] ?? null; 132 | $start = microtime(true); 133 | 134 | $logger->info('deref request', ['url' => $url]); 135 | 136 | try { 137 | $result = $deref->getRedirectLog($url); 138 | $logger->info( 139 | 'deref response', 140 | ['url' => $url, 'result' => $result, 'time_taken' => number_format(microtime(true) - $start, 4)] 141 | ); 142 | } catch (DerefException $e) { 143 | return $response->withJson(['error' => $e->getMessage()], 400); 144 | } catch (\Exception $e) { 145 | return $response->withJson(['error' => 'An unknown error occurred'], 500); 146 | } 147 | 148 | return $response->withJson([ 149 | 'start_url' => $url, 150 | 'final_url' => end($result), 151 | 'final_domain' => parse_url(end($result), PHP_URL_HOST), 152 | 'route_log' => $result, 153 | ], 200); 154 | }); 155 | 156 | $app->run(); 157 | -------------------------------------------------------------------------------- /classes/Deref/Deref.php: -------------------------------------------------------------------------------- 1 | maxHops = $maxHops; 23 | $this->userAgent = $userAgent; 24 | } 25 | 26 | /** 27 | * Follow all the redirects of a URL and return an array containing all results. 28 | * 29 | * This is a recursive method that calls itself until the redirect chain is exhausted or else 30 | * the max recursion depth is reached (>10 redirects, in this case) 31 | * 32 | * @param string $url URL to check 33 | * @param int $depth (internal) Current recursion depth (starts at 0) 34 | * @param bool $checkMeta Whether to check for html-based meta tag redirects 35 | * 36 | * @return array An array of URL matches 37 | * @throws TooManyRedirectsException If recursion depth is exceeded 38 | * @throws InvalidUrlException If URL fails validation 39 | * @throws CommunicationException If a Curl error happens 40 | */ 41 | public function getRedirectLog($url, $depth = 0, $checkMeta = false) 42 | { 43 | if ($depth > $this->maxHops) { 44 | throw new TooManyRedirectsException('Too Many Redirects'); 45 | } 46 | 47 | $url = $this->filterUrl($url); 48 | $curl = $this->getCurlClient($url); 49 | 50 | $startTime = microtime(true); 51 | $result = curl_exec($curl); 52 | $redirect = curl_getinfo($curl, CURLINFO_REDIRECT_URL); 53 | 54 | $logInfo = [ 55 | 'hop' => $depth + 1, 56 | 'timeTaken' => microtime(true) - $startTime, 57 | 'url' => $url, 58 | ]; 59 | 60 | if (!$result) { 61 | $logInfo['curlError'] = curl_error($curl); 62 | $this->logger->notice('Communication failed', $logInfo); 63 | 64 | throw new CommunicationException($logInfo['curlError']); 65 | } 66 | 67 | $this->logger->debug('fetched hop ' . $depth, $logInfo); 68 | 69 | // If this is an HTTP 301/302 redirect response, that means we've got to go deeper 70 | // Recurse with a $depth + 1 to make sure we don't go too deep 71 | if ($redirect) { 72 | return array_merge([$url], $this->getRedirectLog($redirect, $depth + 1, $checkMeta)); 73 | } 74 | 75 | // If this is an HTTP 200 response, we must fetch the body (at least the first few KB) 76 | // and scan for a meta redirect URL 77 | $metaRedirect = $this->checkMetaRedirectUrl($url, $checkMeta); 78 | if ($metaRedirect) { 79 | $this->logger->debug('found meta redirect URL', ['url' => $metaRedirect]); 80 | return array_merge([$url], $this->getRedirectLog($metaRedirect, $depth + 1, $checkMeta)); 81 | } 82 | 83 | return [$url]; 84 | } 85 | 86 | /** 87 | * Examine a URL for HTML "meta" redirects 88 | * 89 | * @param string $url The URL to inspect; ideally, we should already know that this is an HTTP 200 URL 90 | * @param bool $checkMeta Whether to check for the meta redirect tag 91 | * 92 | * @return bool|string False if there is no meta redirect, otherwise the URL 93 | * @throws CommunicationException 94 | */ 95 | public function checkMetaRedirectUrl($url, $checkMeta = true) 96 | { 97 | if (!$checkMeta) { 98 | return false; 99 | } 100 | 101 | $data = fopen('php://memory', 'w+b'); 102 | $curlOptions = []; 103 | 104 | // enable transferring the body data back 105 | $curlOptions[CURLOPT_RETURNTRANSFER] = true; 106 | $curlOptions[CURLOPT_NOBODY] = false; 107 | 108 | // this needs to write to a tmp file so that timed-out content can be read 109 | $curlOptions[CURLOPT_FILE] = $data; 110 | 111 | $curl = $this->getCurlClient($url, $curlOptions); 112 | 113 | $startTime = microtime(true); 114 | curl_exec($curl); 115 | 116 | rewind($data); 117 | $result = stream_get_contents($data); 118 | 119 | fclose($data); 120 | 121 | $logInfo = [ 122 | 'hop' => 'http-200 hop', 123 | 'timeTaken' => microtime(true) - $startTime, 124 | 'url' => $url, 125 | 'bytes' => strlen($result), 126 | ]; 127 | 128 | if (!$result) { 129 | $logInfo['curlError'] = curl_error($curl); 130 | $this->logger->notice('Communication failed', $logInfo); 131 | 132 | throw new CommunicationException($logInfo['curlError']); 133 | } 134 | 135 | $this->logger->debug('fetched http-200', $logInfo); 136 | 137 | // tidy HTML fragment to ensure it can be parsed 138 | // extract metadata and look for a redirect URL 139 | 140 | try { 141 | $crawler = new \Symfony\Component\DomCrawler\Crawler($result); 142 | $content = $crawler->filter('meta[http-equiv=refresh]')->attr('content'); 143 | } catch (\InvalidArgumentException $e) { 144 | } 145 | 146 | if ($content ?? false) { 147 | $this->logger->debug('found meta redirect tag', ['meta-content' => $content]); 148 | $elements = explode(';', $content, 2); 149 | $contentUrl = $elements[1] ?? false; 150 | } 151 | 152 | if ($contentUrl ?? false) { 153 | $elements = explode('=', $contentUrl, 2); 154 | $redirectUrl = $elements[1] ?? false; 155 | } 156 | 157 | return $redirectUrl ?? false; 158 | } 159 | 160 | /** 161 | * Filter URLs to ensure they are http:// or https:// 162 | * If a URL does not provide a scheme, it will be given http:// 163 | * 164 | * @param string $url URL to filter 165 | * 166 | * @return string Filtered URL 167 | * @throws InvalidUrlException If the URL is unusable 168 | */ 169 | public function filterUrl($url) 170 | { 171 | // Ensure the URL is OK to work with 172 | $url_scheme = parse_url($url, PHP_URL_SCHEME); 173 | if (in_array($url_scheme, ['http', 'https'])) { 174 | return $url; 175 | } 176 | 177 | if (!$url_scheme && 'http' === parse_url('http://' . $url, PHP_URL_SCHEME)) { 178 | return 'http://' . $url; 179 | } 180 | 181 | throw new InvalidUrlException('Invalid URL encountered in redirect chain'); 182 | } 183 | 184 | protected function getCurlClient($url, array $options = []) 185 | { 186 | $curl = curl_init($url); 187 | $opts = $options + [ 188 | CURLOPT_FOLLOWLOCATION => false, 189 | CURLOPT_HEADER => false, 190 | CURLOPT_RETURNTRANSFER => false, 191 | CURLOPT_CONNECTTIMEOUT => 2, 192 | CURLOPT_TIMEOUT => 2, 193 | CURLOPT_NOBODY => true, 194 | CURLOPT_USERAGENT => $this->userAgent, 195 | ]; 196 | 197 | // Only allow valid SSL/TLS hosts; don't talk to broken hosts 198 | // This could cause some consternation in the real world, due to the complexity of SSL configuration on servers 199 | // (both those running the code, and those being talked to by the code) 200 | if (parse_url($url, PHP_URL_SCHEME) === 'https') { 201 | $opts[CURLOPT_SSL_VERIFYPEER] = true; 202 | $opts[CURLOPT_SSL_VERIFYHOST] = 2; 203 | } 204 | 205 | curl_setopt_array($curl, $opts); 206 | 207 | return $curl; 208 | } 209 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "5dfb9b28677c9c8458673eea63aeb394", 8 | "packages": [ 9 | { 10 | "name": "monolog/monolog", 11 | "version": "1.26.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/Seldaek/monolog.git", 15 | "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c6b00f05152ae2c9b04a448f99c7590beb6042f5", 20 | "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.3.0", 25 | "psr/log": "~1.0" 26 | }, 27 | "provide": { 28 | "psr/log-implementation": "1.0.0" 29 | }, 30 | "require-dev": { 31 | "aws/aws-sdk-php": "^2.4.9 || ^3.0", 32 | "doctrine/couchdb": "~1.0@dev", 33 | "graylog2/gelf-php": "~1.0", 34 | "php-amqplib/php-amqplib": "~2.4", 35 | "php-console/php-console": "^3.1.3", 36 | "phpstan/phpstan": "^0.12.59", 37 | "phpunit/phpunit": "~4.5", 38 | "ruflin/elastica": ">=0.90 <3.0", 39 | "sentry/sentry": "^0.13", 40 | "swiftmailer/swiftmailer": "^5.3|^6.0" 41 | }, 42 | "suggest": { 43 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", 44 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server", 45 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", 46 | "ext-mongo": "Allow sending log messages to a MongoDB server", 47 | "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", 48 | "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", 49 | "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", 50 | "php-console/php-console": "Allow sending log messages to Google Chrome", 51 | "rollbar/rollbar": "Allow sending log messages to Rollbar", 52 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server", 53 | "sentry/sentry": "Allow sending log messages to a Sentry server" 54 | }, 55 | "type": "library", 56 | "autoload": { 57 | "psr-4": { 58 | "Monolog\\": "src/Monolog" 59 | } 60 | }, 61 | "notification-url": "https://packagist.org/downloads/", 62 | "license": [ 63 | "MIT" 64 | ], 65 | "authors": [ 66 | { 67 | "name": "Jordi Boggiano", 68 | "email": "j.boggiano@seld.be", 69 | "homepage": "http://seld.be" 70 | } 71 | ], 72 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services", 73 | "homepage": "http://github.com/Seldaek/monolog", 74 | "keywords": [ 75 | "log", 76 | "logging", 77 | "psr-3" 78 | ], 79 | "support": { 80 | "issues": "https://github.com/Seldaek/monolog/issues", 81 | "source": "https://github.com/Seldaek/monolog/tree/1.26.1" 82 | }, 83 | "funding": [ 84 | { 85 | "url": "https://github.com/Seldaek", 86 | "type": "github" 87 | }, 88 | { 89 | "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", 90 | "type": "tidelift" 91 | } 92 | ], 93 | "time": "2021-05-28T08:32:12+00:00" 94 | }, 95 | { 96 | "name": "nikic/fast-route", 97 | "version": "v1.3.0", 98 | "source": { 99 | "type": "git", 100 | "url": "https://github.com/nikic/FastRoute.git", 101 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812" 102 | }, 103 | "dist": { 104 | "type": "zip", 105 | "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", 106 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812", 107 | "shasum": "" 108 | }, 109 | "require": { 110 | "php": ">=5.4.0" 111 | }, 112 | "require-dev": { 113 | "phpunit/phpunit": "^4.8.35|~5.7" 114 | }, 115 | "type": "library", 116 | "autoload": { 117 | "psr-4": { 118 | "FastRoute\\": "src/" 119 | }, 120 | "files": [ 121 | "src/functions.php" 122 | ] 123 | }, 124 | "notification-url": "https://packagist.org/downloads/", 125 | "license": [ 126 | "BSD-3-Clause" 127 | ], 128 | "authors": [ 129 | { 130 | "name": "Nikita Popov", 131 | "email": "nikic@php.net" 132 | } 133 | ], 134 | "description": "Fast request router for PHP", 135 | "keywords": [ 136 | "router", 137 | "routing" 138 | ], 139 | "support": { 140 | "issues": "https://github.com/nikic/FastRoute/issues", 141 | "source": "https://github.com/nikic/FastRoute/tree/master" 142 | }, 143 | "time": "2018-02-13T20:26:39+00:00" 144 | }, 145 | { 146 | "name": "pimple/pimple", 147 | "version": "v3.5.0", 148 | "source": { 149 | "type": "git", 150 | "url": "https://github.com/silexphp/Pimple.git", 151 | "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" 152 | }, 153 | "dist": { 154 | "type": "zip", 155 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", 156 | "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", 157 | "shasum": "" 158 | }, 159 | "require": { 160 | "php": ">=7.2.5", 161 | "psr/container": "^1.1 || ^2.0" 162 | }, 163 | "require-dev": { 164 | "symfony/phpunit-bridge": "^5.4@dev" 165 | }, 166 | "type": "library", 167 | "extra": { 168 | "branch-alias": { 169 | "dev-master": "3.4.x-dev" 170 | } 171 | }, 172 | "autoload": { 173 | "psr-0": { 174 | "Pimple": "src/" 175 | } 176 | }, 177 | "notification-url": "https://packagist.org/downloads/", 178 | "license": [ 179 | "MIT" 180 | ], 181 | "authors": [ 182 | { 183 | "name": "Fabien Potencier", 184 | "email": "fabien@symfony.com" 185 | } 186 | ], 187 | "description": "Pimple, a simple Dependency Injection Container", 188 | "homepage": "https://pimple.symfony.com", 189 | "keywords": [ 190 | "container", 191 | "dependency injection" 192 | ], 193 | "support": { 194 | "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" 195 | }, 196 | "time": "2021-10-28T11:13:42+00:00" 197 | }, 198 | { 199 | "name": "psr/container", 200 | "version": "1.1.1", 201 | "source": { 202 | "type": "git", 203 | "url": "https://github.com/php-fig/container.git", 204 | "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" 205 | }, 206 | "dist": { 207 | "type": "zip", 208 | "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", 209 | "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", 210 | "shasum": "" 211 | }, 212 | "require": { 213 | "php": ">=7.2.0" 214 | }, 215 | "type": "library", 216 | "autoload": { 217 | "psr-4": { 218 | "Psr\\Container\\": "src/" 219 | } 220 | }, 221 | "notification-url": "https://packagist.org/downloads/", 222 | "license": [ 223 | "MIT" 224 | ], 225 | "authors": [ 226 | { 227 | "name": "PHP-FIG", 228 | "homepage": "https://www.php-fig.org/" 229 | } 230 | ], 231 | "description": "Common Container Interface (PHP FIG PSR-11)", 232 | "homepage": "https://github.com/php-fig/container", 233 | "keywords": [ 234 | "PSR-11", 235 | "container", 236 | "container-interface", 237 | "container-interop", 238 | "psr" 239 | ], 240 | "support": { 241 | "issues": "https://github.com/php-fig/container/issues", 242 | "source": "https://github.com/php-fig/container/tree/1.1.1" 243 | }, 244 | "time": "2021-03-05T17:36:06+00:00" 245 | }, 246 | { 247 | "name": "psr/http-message", 248 | "version": "1.0.1", 249 | "source": { 250 | "type": "git", 251 | "url": "https://github.com/php-fig/http-message.git", 252 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 253 | }, 254 | "dist": { 255 | "type": "zip", 256 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 257 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 258 | "shasum": "" 259 | }, 260 | "require": { 261 | "php": ">=5.3.0" 262 | }, 263 | "type": "library", 264 | "extra": { 265 | "branch-alias": { 266 | "dev-master": "1.0.x-dev" 267 | } 268 | }, 269 | "autoload": { 270 | "psr-4": { 271 | "Psr\\Http\\Message\\": "src/" 272 | } 273 | }, 274 | "notification-url": "https://packagist.org/downloads/", 275 | "license": [ 276 | "MIT" 277 | ], 278 | "authors": [ 279 | { 280 | "name": "PHP-FIG", 281 | "homepage": "http://www.php-fig.org/" 282 | } 283 | ], 284 | "description": "Common interface for HTTP messages", 285 | "homepage": "https://github.com/php-fig/http-message", 286 | "keywords": [ 287 | "http", 288 | "http-message", 289 | "psr", 290 | "psr-7", 291 | "request", 292 | "response" 293 | ], 294 | "support": { 295 | "source": "https://github.com/php-fig/http-message/tree/master" 296 | }, 297 | "time": "2016-08-06T14:39:51+00:00" 298 | }, 299 | { 300 | "name": "psr/log", 301 | "version": "1.1.4", 302 | "source": { 303 | "type": "git", 304 | "url": "https://github.com/php-fig/log.git", 305 | "reference": "d49695b909c3b7628b6289db5479a1c204601f11" 306 | }, 307 | "dist": { 308 | "type": "zip", 309 | "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", 310 | "reference": "d49695b909c3b7628b6289db5479a1c204601f11", 311 | "shasum": "" 312 | }, 313 | "require": { 314 | "php": ">=5.3.0" 315 | }, 316 | "type": "library", 317 | "extra": { 318 | "branch-alias": { 319 | "dev-master": "1.1.x-dev" 320 | } 321 | }, 322 | "autoload": { 323 | "psr-4": { 324 | "Psr\\Log\\": "Psr/Log/" 325 | } 326 | }, 327 | "notification-url": "https://packagist.org/downloads/", 328 | "license": [ 329 | "MIT" 330 | ], 331 | "authors": [ 332 | { 333 | "name": "PHP-FIG", 334 | "homepage": "https://www.php-fig.org/" 335 | } 336 | ], 337 | "description": "Common interface for logging libraries", 338 | "homepage": "https://github.com/php-fig/log", 339 | "keywords": [ 340 | "log", 341 | "psr", 342 | "psr-3" 343 | ], 344 | "support": { 345 | "source": "https://github.com/php-fig/log/tree/1.1.4" 346 | }, 347 | "time": "2021-05-03T11:20:27+00:00" 348 | }, 349 | { 350 | "name": "slim/slim", 351 | "version": "3.12.3", 352 | "source": { 353 | "type": "git", 354 | "url": "https://github.com/slimphp/Slim.git", 355 | "reference": "1c9318a84ffb890900901136d620b4f03a59da38" 356 | }, 357 | "dist": { 358 | "type": "zip", 359 | "url": "https://api.github.com/repos/slimphp/Slim/zipball/1c9318a84ffb890900901136d620b4f03a59da38", 360 | "reference": "1c9318a84ffb890900901136d620b4f03a59da38", 361 | "shasum": "" 362 | }, 363 | "require": { 364 | "ext-json": "*", 365 | "ext-libxml": "*", 366 | "ext-simplexml": "*", 367 | "nikic/fast-route": "^1.0", 368 | "php": ">=5.5.0", 369 | "pimple/pimple": "^3.0", 370 | "psr/container": "^1.0", 371 | "psr/http-message": "^1.0" 372 | }, 373 | "provide": { 374 | "psr/http-message-implementation": "1.0" 375 | }, 376 | "require-dev": { 377 | "phpunit/phpunit": "^4.0", 378 | "squizlabs/php_codesniffer": "^2.5" 379 | }, 380 | "type": "library", 381 | "autoload": { 382 | "psr-4": { 383 | "Slim\\": "Slim" 384 | } 385 | }, 386 | "notification-url": "https://packagist.org/downloads/", 387 | "license": [ 388 | "MIT" 389 | ], 390 | "authors": [ 391 | { 392 | "name": "Josh Lockhart", 393 | "email": "hello@joshlockhart.com", 394 | "homepage": "https://joshlockhart.com" 395 | }, 396 | { 397 | "name": "Andrew Smith", 398 | "email": "a.smith@silentworks.co.uk", 399 | "homepage": "http://silentworks.co.uk" 400 | }, 401 | { 402 | "name": "Rob Allen", 403 | "email": "rob@akrabat.com", 404 | "homepage": "http://akrabat.com" 405 | }, 406 | { 407 | "name": "Gabriel Manricks", 408 | "email": "gmanricks@me.com", 409 | "homepage": "http://gabrielmanricks.com" 410 | } 411 | ], 412 | "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", 413 | "homepage": "https://slimframework.com", 414 | "keywords": [ 415 | "api", 416 | "framework", 417 | "micro", 418 | "router" 419 | ], 420 | "support": { 421 | "issues": "https://github.com/slimphp/Slim/issues", 422 | "source": "https://github.com/slimphp/Slim/tree/3.x" 423 | }, 424 | "time": "2019-11-28T17:40:33+00:00" 425 | }, 426 | { 427 | "name": "symfony/css-selector", 428 | "version": "v3.4.47", 429 | "source": { 430 | "type": "git", 431 | "url": "https://github.com/symfony/css-selector.git", 432 | "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33" 433 | }, 434 | "dist": { 435 | "type": "zip", 436 | "url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33", 437 | "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33", 438 | "shasum": "" 439 | }, 440 | "require": { 441 | "php": "^5.5.9|>=7.0.8" 442 | }, 443 | "type": "library", 444 | "autoload": { 445 | "psr-4": { 446 | "Symfony\\Component\\CssSelector\\": "" 447 | }, 448 | "exclude-from-classmap": [ 449 | "/Tests/" 450 | ] 451 | }, 452 | "notification-url": "https://packagist.org/downloads/", 453 | "license": [ 454 | "MIT" 455 | ], 456 | "authors": [ 457 | { 458 | "name": "Fabien Potencier", 459 | "email": "fabien@symfony.com" 460 | }, 461 | { 462 | "name": "Jean-François Simon", 463 | "email": "jeanfrancois.simon@sensiolabs.com" 464 | }, 465 | { 466 | "name": "Symfony Community", 467 | "homepage": "https://symfony.com/contributors" 468 | } 469 | ], 470 | "description": "Symfony CssSelector Component", 471 | "homepage": "https://symfony.com", 472 | "support": { 473 | "source": "https://github.com/symfony/css-selector/tree/v3.4.47" 474 | }, 475 | "funding": [ 476 | { 477 | "url": "https://symfony.com/sponsor", 478 | "type": "custom" 479 | }, 480 | { 481 | "url": "https://github.com/fabpot", 482 | "type": "github" 483 | }, 484 | { 485 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 486 | "type": "tidelift" 487 | } 488 | ], 489 | "time": "2020-10-24T10:57:07+00:00" 490 | }, 491 | { 492 | "name": "symfony/dom-crawler", 493 | "version": "v3.4.47", 494 | "source": { 495 | "type": "git", 496 | "url": "https://github.com/symfony/dom-crawler.git", 497 | "reference": "ef97bcfbae5b384b4ca6c8d57b617722f15241a6" 498 | }, 499 | "dist": { 500 | "type": "zip", 501 | "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ef97bcfbae5b384b4ca6c8d57b617722f15241a6", 502 | "reference": "ef97bcfbae5b384b4ca6c8d57b617722f15241a6", 503 | "shasum": "" 504 | }, 505 | "require": { 506 | "php": "^5.5.9|>=7.0.8", 507 | "symfony/polyfill-ctype": "~1.8", 508 | "symfony/polyfill-mbstring": "~1.0" 509 | }, 510 | "require-dev": { 511 | "symfony/css-selector": "~2.8|~3.0|~4.0" 512 | }, 513 | "suggest": { 514 | "symfony/css-selector": "" 515 | }, 516 | "type": "library", 517 | "autoload": { 518 | "psr-4": { 519 | "Symfony\\Component\\DomCrawler\\": "" 520 | }, 521 | "exclude-from-classmap": [ 522 | "/Tests/" 523 | ] 524 | }, 525 | "notification-url": "https://packagist.org/downloads/", 526 | "license": [ 527 | "MIT" 528 | ], 529 | "authors": [ 530 | { 531 | "name": "Fabien Potencier", 532 | "email": "fabien@symfony.com" 533 | }, 534 | { 535 | "name": "Symfony Community", 536 | "homepage": "https://symfony.com/contributors" 537 | } 538 | ], 539 | "description": "Symfony DomCrawler Component", 540 | "homepage": "https://symfony.com", 541 | "support": { 542 | "source": "https://github.com/symfony/dom-crawler/tree/v3.4.47" 543 | }, 544 | "funding": [ 545 | { 546 | "url": "https://symfony.com/sponsor", 547 | "type": "custom" 548 | }, 549 | { 550 | "url": "https://github.com/fabpot", 551 | "type": "github" 552 | }, 553 | { 554 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 555 | "type": "tidelift" 556 | } 557 | ], 558 | "time": "2020-10-24T10:57:07+00:00" 559 | }, 560 | { 561 | "name": "symfony/polyfill-ctype", 562 | "version": "v1.24.0", 563 | "source": { 564 | "type": "git", 565 | "url": "https://github.com/symfony/polyfill-ctype.git", 566 | "reference": "30885182c981ab175d4d034db0f6f469898070ab" 567 | }, 568 | "dist": { 569 | "type": "zip", 570 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", 571 | "reference": "30885182c981ab175d4d034db0f6f469898070ab", 572 | "shasum": "" 573 | }, 574 | "require": { 575 | "php": ">=7.1" 576 | }, 577 | "provide": { 578 | "ext-ctype": "*" 579 | }, 580 | "suggest": { 581 | "ext-ctype": "For best performance" 582 | }, 583 | "type": "library", 584 | "extra": { 585 | "branch-alias": { 586 | "dev-main": "1.23-dev" 587 | }, 588 | "thanks": { 589 | "name": "symfony/polyfill", 590 | "url": "https://github.com/symfony/polyfill" 591 | } 592 | }, 593 | "autoload": { 594 | "psr-4": { 595 | "Symfony\\Polyfill\\Ctype\\": "" 596 | }, 597 | "files": [ 598 | "bootstrap.php" 599 | ] 600 | }, 601 | "notification-url": "https://packagist.org/downloads/", 602 | "license": [ 603 | "MIT" 604 | ], 605 | "authors": [ 606 | { 607 | "name": "Gert de Pagter", 608 | "email": "BackEndTea@gmail.com" 609 | }, 610 | { 611 | "name": "Symfony Community", 612 | "homepage": "https://symfony.com/contributors" 613 | } 614 | ], 615 | "description": "Symfony polyfill for ctype functions", 616 | "homepage": "https://symfony.com", 617 | "keywords": [ 618 | "compatibility", 619 | "ctype", 620 | "polyfill", 621 | "portable" 622 | ], 623 | "support": { 624 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" 625 | }, 626 | "funding": [ 627 | { 628 | "url": "https://symfony.com/sponsor", 629 | "type": "custom" 630 | }, 631 | { 632 | "url": "https://github.com/fabpot", 633 | "type": "github" 634 | }, 635 | { 636 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 637 | "type": "tidelift" 638 | } 639 | ], 640 | "time": "2021-10-20T20:35:02+00:00" 641 | }, 642 | { 643 | "name": "symfony/polyfill-mbstring", 644 | "version": "v1.24.0", 645 | "source": { 646 | "type": "git", 647 | "url": "https://github.com/symfony/polyfill-mbstring.git", 648 | "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" 649 | }, 650 | "dist": { 651 | "type": "zip", 652 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", 653 | "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", 654 | "shasum": "" 655 | }, 656 | "require": { 657 | "php": ">=7.1" 658 | }, 659 | "provide": { 660 | "ext-mbstring": "*" 661 | }, 662 | "suggest": { 663 | "ext-mbstring": "For best performance" 664 | }, 665 | "type": "library", 666 | "extra": { 667 | "branch-alias": { 668 | "dev-main": "1.23-dev" 669 | }, 670 | "thanks": { 671 | "name": "symfony/polyfill", 672 | "url": "https://github.com/symfony/polyfill" 673 | } 674 | }, 675 | "autoload": { 676 | "psr-4": { 677 | "Symfony\\Polyfill\\Mbstring\\": "" 678 | }, 679 | "files": [ 680 | "bootstrap.php" 681 | ] 682 | }, 683 | "notification-url": "https://packagist.org/downloads/", 684 | "license": [ 685 | "MIT" 686 | ], 687 | "authors": [ 688 | { 689 | "name": "Nicolas Grekas", 690 | "email": "p@tchwork.com" 691 | }, 692 | { 693 | "name": "Symfony Community", 694 | "homepage": "https://symfony.com/contributors" 695 | } 696 | ], 697 | "description": "Symfony polyfill for the Mbstring extension", 698 | "homepage": "https://symfony.com", 699 | "keywords": [ 700 | "compatibility", 701 | "mbstring", 702 | "polyfill", 703 | "portable", 704 | "shim" 705 | ], 706 | "support": { 707 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0" 708 | }, 709 | "funding": [ 710 | { 711 | "url": "https://symfony.com/sponsor", 712 | "type": "custom" 713 | }, 714 | { 715 | "url": "https://github.com/fabpot", 716 | "type": "github" 717 | }, 718 | { 719 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 720 | "type": "tidelift" 721 | } 722 | ], 723 | "time": "2021-11-30T18:21:41+00:00" 724 | } 725 | ], 726 | "packages-dev": [], 727 | "aliases": [], 728 | "minimum-stability": "stable", 729 | "stability-flags": [], 730 | "prefer-stable": false, 731 | "prefer-lowest": false, 732 | "platform": { 733 | "php": ">=7.0.0", 734 | "ext-curl": "*" 735 | }, 736 | "platform-dev": [], 737 | "plugin-api-version": "2.1.0" 738 | } 739 | --------------------------------------------------------------------------------