From c32d434ae6486dadc2c839c1e3326cd26393f2de Mon Sep 17 00:00:00 2001 From: Mathias Brodala Date: Wed, 27 May 2026 10:30:24 +0200 Subject: [PATCH] Optionally follow redirects --- README.md | 21 +++++++++++++-------- bin/cigar | 18 ++++++++++-------- spec/AsyncCheckerSpec.php | 30 ++++++++++++++++++++++++++++++ spec/CigarCliSpec.php | 16 +++++++++++++++- spec/stubs/.cigar.redirects.json | 22 ++++++++++++++++++++++ src/AsyncChecker.php | 12 +++++++++++- 6 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 spec/stubs/.cigar.redirects.json diff --git a/README.md b/README.md index 9c88cd1..27fa517 100755 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ Run `vendor/bin/cigar` to have it check each of the URLs return the status code Run `docker run -v $(pwd):/app --rm brunty/cigar` to have it check each of the URLs return the status code expected. ``` -> vendor/bin/cigar +> vendor/bin/cigar ✓ http://httpbin.org/status/418 [418:418] teapot -✓ http://httpbin.org/status/200 [200:200] [text/html:text/html] -✓ http://httpbin.org/status/304 [304:304] -✓ http://httpbin.org/status/500 [500:500] +✓ http://httpbin.org/status/200 [200:200] [text/html:text/html] +✓ http://httpbin.org/status/304 [304:304] +✓ http://httpbin.org/status/500 [500:500] ``` The format of the lines in the output is: @@ -79,7 +79,7 @@ If you wish to use an alternative configuration file, use the `vendor/bin/cigar ### Passing a base URL to check against -If you wish to check a file of URLs relative to the root of a site against a base URL, you can do so with by using +If you wish to check a file of URLs relative to the root of a site against a base URL, you can do so with by using `vendor/bin/cigar -u http://httpbin.org` or `vendor/bin/cigar --url=http://httpbin.org` Your configuration file can then contain URLs including: @@ -105,7 +105,7 @@ Your configuration file can then contain URLs including: "status": 418, "content": "teapot" }, - + { "url": "status/418", "status": 418, @@ -116,14 +116,19 @@ Your configuration file can then contain URLs including: ### Disabling SSL cert verification -If you wish to run the tool without checking SSL certs, use the `-i` or `--insecure` option to the command: +If you wish to run the tool without checking SSL certs, use the `-i` or `--insecure` option to the command: `vendor/bin/cigar -i` or `vendor/bin/cigar --insecure` **Only use this if absolutely necessary.** +### Following redirects + +If you wish to follow redirects, use the `-r` or `--follow-redirects` option in the command: +`vendor/bin/cigar -r` or `vendor/bin/cigar --follow-redirects` + ### Passing Authorization header -If you wish to add the Authorization header, use the `-a` or `--auth` option to the command: +If you wish to add the Authorization header, use the `-a` or `--auth` option to the command: `vendor/bin/cigar -a "Basic dXNyOnBzd2Q="` or `vendor/bin/cigar --auth="Basic dXNyOnBzd2Q="` ### Passing custom header diff --git a/bin/cigar b/bin/cigar index 13e5a73..b2409eb 100755 --- a/bin/cigar +++ b/bin/cigar @@ -18,7 +18,7 @@ use Brunty\Cigar\Outputter; use Brunty\Cigar\Parser; use Brunty\Cigar\Result; -$options = getopt('c:ia:u:jh:t:s:', ['version', 'help', 'quiet', 'config:', 'insecure', 'auth:', 'url:', 'json', 'header:', 'timeout:', 'connect-timeout:']); +$options = getopt('c:ira:u:jh:t:s:', ['version', 'help', 'quiet', 'config:', 'insecure', 'follow-redirects', 'auth:', 'url:', 'json', 'header:', 'timeout:', 'connect-timeout:']); if (isset($options['help'])) { $content = <<check($domains); +$results = (new AsyncChecker($secure, $followRedirects, $authorization, $headers))->check($domains); $passedResults = array_filter($results, function (Result $result) { return $result->hasPassed(); }); diff --git a/spec/AsyncCheckerSpec.php b/spec/AsyncCheckerSpec.php index 3bc0060..e994112 100644 --- a/spec/AsyncCheckerSpec.php +++ b/spec/AsyncCheckerSpec.php @@ -63,6 +63,36 @@ }); }); + context('when redirects should not be followed', function () { + it('returns redirect status code', function () { + $domain = new Url('https://httpbin.org/redirect-to?url=https://httpbin.org/status/200&status_code=301', 200); + $domains = [$domain]; + + $results = (new AsyncChecker())->check($domains); + + $expected = [ + new Result($domain, 301), + ]; + + expect($results[0]->getStatusCode())->toEqual($expected[0]->getStatusCode()); + }); + }); + + context('when redirects should be followed', function () { + it('returns redirect status code', function () { + $domain = new Url('https://httpbin.org/redirect-to?status_code=301&url=https://httpbin.org/status/200', 200); + $domains = [$domain]; + + $results = (new AsyncChecker(true, true))->check($domains); + + $expected = [ + new Result($domain, 200), + ]; + + expect($results[0]->getStatusCode())->toEqual($expected[0]->getStatusCode()); + }); + }); + context('when timeouts are set', function () { it('checks a URL that will timeout', function () { // Need to change for a better setup URL that doesn't default to a potentially unknown site diff --git a/spec/CigarCliSpec.php b/spec/CigarCliSpec.php index 97fe9b4..5d22d35 100755 --- a/spec/CigarCliSpec.php +++ b/spec/CigarCliSpec.php @@ -52,7 +52,7 @@ expect($process->getExitCode())->toBe(1); }); - + it('passes with insecure certs if the insecure flag is specified', function () { $process = new Process('cd spec && cp stubs/.cigar.insecure.json .cigar.json && ../bin/cigar --insecure'); $process->run(); @@ -60,6 +60,20 @@ expect($process->getExitCode())->toBe(0); }); + it('fails with redirects', function () { + $process = new Process('cd spec && cp stubs/.cigar.redirects.json .cigar.json && ../bin/cigar'); + $process->run(); + + expect($process->getExitCode())->toBe(1); + }); + + it('passes with redirects certs if the follow-redirects flag is specified', function () { + $process = new Process('cd spec && cp stubs/.cigar.redirects.json .cigar.json && ../bin/cigar --follow-redirects'); + $process->run(); + + expect($process->getExitCode())->toBe(0); + }); + it('can be passed a base URL as a short command argument', function () { $process = new Process('cd spec && cp stubs/.cigar.without-base.json .cigar.json && ../bin/cigar -u http://httpbin.org'); $process->run(); diff --git a/spec/stubs/.cigar.redirects.json b/spec/stubs/.cigar.redirects.json new file mode 100644 index 0000000..882329d --- /dev/null +++ b/spec/stubs/.cigar.redirects.json @@ -0,0 +1,22 @@ +[ + { + "url": "https://httpbin.org/redirect-to?status_code=301&url=https://httpbin.org/status/200", + "status": 200 + }, + { + "url": "https://httpbin.org/redirect-to?status_code=302&url=https://httpbin.org/status/200", + "status": 200 + }, + { + "url": "https://httpbin.org/redirect-to?status_code=303&url=https://httpbin.org/status/200", + "status": 200 + }, + { + "url": "https://httpbin.org/redirect-to?status_code=307&url=https://httpbin.org/status/200", + "status": 200 + }, + { + "url": "https://httpbin.org/redirect-to?status_code=308&url=https://httpbin.org/status/200", + "status": 200 + } +] \ No newline at end of file diff --git a/src/AsyncChecker.php b/src/AsyncChecker.php index a95ac8c..7d0a766 100644 --- a/src/AsyncChecker.php +++ b/src/AsyncChecker.php @@ -10,6 +10,11 @@ class AsyncChecker */ private $checkSsl; + /** + * @var bool + */ + private $followRedirects; + /** * @var string */ @@ -20,9 +25,10 @@ class AsyncChecker */ private $headers; - public function __construct(bool $checkSsl = true, string $authorizationHeader = null, array $headers = []) + public function __construct(bool $checkSsl = true, bool $followRedirects = false, string $authorizationHeader = null, array $headers = []) { $this->checkSsl = $checkSsl; + $this->followRedirects = $followRedirects; $this->authorizationHeader = $authorizationHeader; $this->headers = $headers; } @@ -49,6 +55,10 @@ public function check(array $urlsToCheck): array curl_setopt($channel, CURLOPT_SSL_VERIFYHOST, 0); } + if ($this->followRedirects) { + curl_setopt($channel, CURLOPT_FOLLOWLOCATION, 1); + } + if ($this->authorizationHeader) { $this->headers[] = "Authorization: {$this->authorizationHeader}"; }