#!/usr/local/lib/php5.4/bin/php array( 'autoregister_zf' => true, ), )); use Zend\Http\Client; use Zend\Http\Request; class ColorText { private $foregroundColors = array(); private $backgroundColors = array(); public function __construct() { // Set up shell colors $this->foregroundColors['black'] = '0;30'; $this->foregroundColors['dark_gray'] = '1;30'; $this->foregroundColors['blue'] = '0;34'; $this->foregroundColors['light_blue'] = '1;34'; $this->foregroundColors['green'] = '0;32'; $this->foregroundColors['light_green'] = '1;32'; $this->foregroundColors['cyan'] = '0;36'; $this->foregroundColors['light_cyan'] = '1;36'; $this->foregroundColors['red'] = '0;31'; $this->foregroundColors['light_red'] = '1;31'; $this->foregroundColors['purple'] = '0;35'; $this->foregroundColors['light_purple'] = '1;35'; $this->foregroundColors['brown'] = '0;33'; $this->foregroundColors['yellow'] = '1;33'; $this->foregroundColors['light_gray'] = '0;37'; $this->foregroundColors['white'] = '1;37'; $this->backgroundColors['black'] = '40'; $this->backgroundColors['red'] = '41'; $this->backgroundColors['green'] = '42'; $this->backgroundColors['yellow'] = '43'; $this->backgroundColors['blue'] = '44'; $this->backgroundColors['magenta'] = '45'; $this->backgroundColors['cyan'] = '46'; $this->backgroundColors['light_gray'] = '47'; } // Returns colored string public function getColoredString($string, $foreground_color = null, $background_color = null) { $colored_string = ""; // Check if given foreground color found if (isset($this->foregroundColors[$foreground_color])) { $colored_string .= "\033[" . $this->foregroundColors[$foreground_color] . "m"; } // Check if given background color found if (isset($this->backgroundColors[$background_color])) { $colored_string .= "\033[" . $this->backgroundColors[$background_color] . "m"; } // Add string and end coloring $colored_string .= $string . "\033[0m"; return $colored_string; } // Returns all foreground color names public function getForegroundColors() { return array_keys($this->foregroundColors); } // Returns all background color names public function getBackgroundColors() { return array_keys($this->backgroundColors); } } class HttpRedirectTest { const VERSION = '1.0'; const TYPE_SRC_URL = 'SRC URL'; const TYPE_DST_URL = 'DST URL'; const TYPE_DST_URL_MATCH = 'DST Url Match'; /** * @var Zend\Http\Client */ protected $client; protected $clientDefaultOptions = [ 'maxredirects' => 0, 'timeout' => 10 ]; protected $validLocationUrlCodes = [301, 302, 200]; protected $index; protected $line; protected $logFormat = "|%-4s | %-13s | %-120s | %-40s | %-7s |\n"; protected $logHeaders = array('Line', 'Test Type', 'Source URL', 'Message', 'Status'); protected $logLineLength = 198; protected $text; protected $_user; protected $_password; protected $youTrackFormat = false; function __construct($options = array()) { $this->client = new Client(null, array_merge($this->clientDefaultOptions, $options)); $this->client->getRequest()->setMethod(Request::METHOD_HEAD); $this->text = new ColorText(); } /** * @param boolean $youTrackFormat * @return $this */ public function setYouTrackFormat($youTrackFormat) { $this->youTrackFormat = (bool) $youTrackFormat; return $this; } /** * @return boolean */ public function getYouTrackFormat() { return $this->youTrackFormat; } public function head($url) { $this->_initCredentials(); return $this->client->setUri($url)->send(); } public function validateRedirect($sourceUrl, $destinationUrl, array $flags = array()) { if (empty($flags['R'])) { $this->log(self::TYPE_DST_URL, $destinationUrl, 'R flag is undefined', false); return false; } $response = $this->head($sourceUrl); // Validate Source URL Redirect Code if ($flags['R'] != ($code = $response->getStatusCode())) { $this->log( self::TYPE_SRC_URL, $this->_formatUrl($sourceUrl), 'expected HTTP code ' . $flags['R'] . ' instead of ' . $code, false ); return false; } /** @var $location Zend\Http\Header\Location */ if (!($location = $response->getHeaders()->get('Location')) || !($locationUrl = $location->getFieldValue())) { $this->log(self::TYPE_SRC_URL, $this->_formatUrl($sourceUrl), '"Location" header is undefined', false); return false; } // Validate Location $locationResponse = $this->head($locationUrl); if (!($isValidLocationCode = in_array($locationResponse->getStatusCode(), $this->validLocationUrlCodes))) { $message = 'unexpected HTTP code ' . $locationResponse->getStatusCode(); } else { $message = 'HTTP ' . $locationResponse->getStatusCode(); } $isDestinationUrlMatch = $this->testUrlMatch($locationUrl, $destinationUrl); $this->log(self::TYPE_SRC_URL, $this->_formatUrl($sourceUrl), 'HTTP ' . $code, true); $this->log( self::TYPE_DST_URL, ' --> ' . $this->_formatUrl($locationUrl), $message, $isValidLocationCode ); $this->log(self::TYPE_DST_URL_MATCH, ' --> ' . $this->_formatUrl($destinationUrl), null, $isDestinationUrlMatch); return true; } protected function testUrlMatch($locationUrl, $destinationUrl) { if ('?' == substr($destinationUrl, -1)) { $destinationUrl = substr($destinationUrl, 0, -1); } return rawurldecode($locationUrl) == rawurldecode($destinationUrl); } public function log($testType, $url, $message, $isValid = null) { if (null === $isValid) { $status = ''; } else { $status = $isValid ? 'passed' : 'failed'; } $format = $this->logFormat; $foregroundColor = null; $backgroundColor = null; if (false === $isValid) { $foregroundColor = 'light_red'; } if ($foregroundColor || $backgroundColor) { $format = $this->text->getColoredString($this->logFormat, $foregroundColor, $backgroundColor); } printf($format, $this->line + 1, $testType, $url, $message, $status); } protected function _formatUrl($url) { if (!$this->getYouTrackFormat()) { return $url; } if (strlen($url) > 80) { $text = substr($url, 0, 10) . '...' . substr($url, -68); return '[' . $url . ' ' . $text . ']'; } return $url; } protected function getHeaderFormat() { if ($this->getYouTrackFormat()) { return str_replace('|', '||', $this->logFormat); } return $this->logFormat; } public function run($file) { $srcUrls = []; $lines = file($file); $prevLine = null; vprintf($this->getHeaderFormat(), $this->logHeaders); $this->index = 0; foreach ($lines as $this->line => $line) { $line = trim($line); if (empty($line)) { $srcUrls = []; continue; } if ($line[0] == '#' && ($url = trim(ltrim($line, '# '), '\n')) && 0 === strpos($url, 'http')) { $srcUrls[] = $url; } if (empty($srcUrls) || 0 !== strncmp($line, 'RewriteRule', 11)) { continue; } $matches = array(); if (!preg_match('/^RewriteRule\s+([^\s]+)\s+([^\s]+)(?:\s+\[([^\s]+)\])/', $line, $matches)) { continue; } list(,, $substitution, $flags) = $matches; $this->index++; foreach ($srcUrls as $sourceUrl) { $this->validateRedirect($sourceUrl, $substitution, $this->_parseFlags($flags)); } $srcUrls = []; } } protected function _parseFlags($value) { $fields = array(); $parts = explode(',', $value); foreach ($parts as $part) { $list = explode('=', $part); $fields[$list[0]] = isset($list[1]) ? $list[1] : null; } return $fields; } protected function _initCredentials() { if (!empty($this->_user) && !empty($this->_password)) { $this->client->setAuth($this->_user, $this->_password); } return $this; } public function setCredentials($user, $password) { $this->_user = $user; $this->_password = $password; return $this; } } // MAIN $opts = new Zend\Console\Getopt([ 'user|u-s' => 'Basic Auth user', 'password|p-s' => 'Basic Auth password', 'f' => 'format output using YouTrack markup', 'version|v' => 'version', 'help|h' => 'help', ]); $printHelp = function () use ($opts) { $message = $opts->getUsageMessage(); $message = str_replace('[ options ]', 'FILE [ options ]' . PHP_EOL, $message); echo APPLICATION_ID . PHP_EOL . PHP_EOL . $message . PHP_EOL . 'Example: hart .htaccess -u tester -p test -f' . PHP_EOL . PHP_EOL; }; try { $opts->parse(); } catch (Exception $e) { echo $e->getMessage(), PHP_EOL, PHP_EOL; $printHelp(); exit; } if ($opts->getOption('h')) { die($opts->getUsageMessage()); } if ($opts->getOption('v')) { die('Version: ' . APPLICATION_ID . PHP_EOL); } if (!($opts->getRemainingArgs())) { $printHelp(); exit; } $file = current($opts->getRemainingArgs()); $test = new HttpRedirectTest(); if ($opts->getOption('f')) { $test->setYouTrackFormat(true); } if (($user = $opts->getOption('u')) && ($password = $opts->getOption('p'))) { $test->setCredentials($user, $password); } $test->run($file); echo PHP_EOL;