diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -126,6 +126,8 @@ 'PhutilAuthUserAbortedException' => 'auth/exception/PhutilAuthUserAbortedException.php', 'PhutilBacktraceSignalHandler' => 'future/exec/PhutilBacktraceSignalHandler.php', 'PhutilBallOfPHP' => 'phage/util/PhutilBallOfPHP.php', + 'PhutilBinaryAnalyzer' => 'filesystem/binary/PhutilBinaryAnalyzer.php', + 'PhutilBinaryAnalyzerTestCase' => 'filesystem/binary/__tests__/PhutilBinaryAnalyzerTestCase.php', 'PhutilBitbucketAuthAdapter' => 'auth/PhutilBitbucketAuthAdapter.php', 'PhutilBootloader' => 'moduleutils/PhutilBootloader.php', 'PhutilBootloaderException' => 'moduleutils/PhutilBootloaderException.php', @@ -195,6 +197,7 @@ 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php', 'PhutilDeferredLog' => 'filesystem/PhutilDeferredLog.php', 'PhutilDeferredLogTestCase' => 'filesystem/__tests__/PhutilDeferredLogTestCase.php', + 'PhutilDiffBinaryAnalyzer' => 'filesystem/binary/PhutilDiffBinaryAnalyzer.php', 'PhutilDirectedScalarGraph' => 'utils/PhutilDirectedScalarGraph.php', 'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php', 'PhutilDirectoryKeyValueCache' => 'cache/PhutilDirectoryKeyValueCache.php', @@ -233,6 +236,7 @@ 'PhutilFileTree' => 'filesystem/PhutilFileTree.php', 'PhutilFrenchLocale' => 'internationalization/locales/PhutilFrenchLocale.php', 'PhutilGermanLocale' => 'internationalization/locales/PhutilGermanLocale.php', + 'PhutilGitBinaryAnalyzer' => 'filesystem/binary/PhutilGitBinaryAnalyzer.php', 'PhutilGitHubAuthAdapter' => 'auth/PhutilGitHubAuthAdapter.php', 'PhutilGitHubFuture' => 'future/github/PhutilGitHubFuture.php', 'PhutilGitHubResponse' => 'future/github/PhutilGitHubResponse.php', @@ -300,6 +304,7 @@ 'PhutilMarkupEngine' => 'markup/PhutilMarkupEngine.php', 'PhutilMarkupTestCase' => 'markup/__tests__/PhutilMarkupTestCase.php', 'PhutilMemcacheKeyValueCache' => 'cache/PhutilMemcacheKeyValueCache.php', + 'PhutilMercurialBinaryAnalyzer' => 'filesystem/binary/PhutilMercurialBinaryAnalyzer.php', 'PhutilMethodNotImplementedException' => 'error/PhutilMethodNotImplementedException.php', 'PhutilMetricsChannel' => 'channel/PhutilMetricsChannel.php', 'PhutilMissingSymbolException' => 'symbols/exception/PhutilMissingSymbolException.php', @@ -339,6 +344,7 @@ 'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php', 'PhutilProxyException' => 'error/PhutilProxyException.php', 'PhutilProxyIterator' => 'utils/PhutilProxyIterator.php', + 'PhutilPygmentizeBinaryAnalyzer' => 'filesystem/binary/PhutilPygmentizeBinaryAnalyzer.php', 'PhutilPygmentizeParser' => 'parser/PhutilPygmentizeParser.php', 'PhutilPygmentizeParserTestCase' => 'parser/__tests__/PhutilPygmentizeParserTestCase.php', 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', @@ -411,6 +417,7 @@ 'PhutilSprite' => 'sprites/PhutilSprite.php', 'PhutilSpriteSheet' => 'sprites/PhutilSpriteSheet.php', 'PhutilStreamIterator' => 'utils/PhutilStreamIterator.php', + 'PhutilSubversionBinaryAnalyzer' => 'filesystem/binary/PhutilSubversionBinaryAnalyzer.php', 'PhutilSymbolLoader' => 'symbols/PhutilSymbolLoader.php', 'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php', 'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php', @@ -729,6 +736,8 @@ 'PhutilAuthUserAbortedException' => 'PhutilAuthException', 'PhutilBacktraceSignalHandler' => 'PhutilSignalHandler', 'PhutilBallOfPHP' => 'Phobject', + 'PhutilBinaryAnalyzer' => 'Phobject', + 'PhutilBinaryAnalyzerTestCase' => 'PhutilTestCase', 'PhutilBitbucketAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilBootloaderException' => 'Exception', 'PhutilBritishEnglishLocale' => 'PhutilLocale', @@ -803,6 +812,7 @@ 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase', 'PhutilDeferredLog' => 'Phobject', 'PhutilDeferredLogTestCase' => 'PhutilTestCase', + 'PhutilDiffBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilDirectedScalarGraph' => 'AbstractDirectedGraph', 'PhutilDirectoryFixture' => 'Phobject', 'PhutilDirectoryKeyValueCache' => 'PhutilKeyValueCache', @@ -841,6 +851,7 @@ 'PhutilFileTree' => 'Phobject', 'PhutilFrenchLocale' => 'PhutilLocale', 'PhutilGermanLocale' => 'PhutilLocale', + 'PhutilGitBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilGitHubAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilGitHubFuture' => 'FutureProxy', 'PhutilGitHubResponse' => 'Phobject', @@ -911,6 +922,7 @@ 'PhutilMarkupEngine' => 'Phobject', 'PhutilMarkupTestCase' => 'PhutilTestCase', 'PhutilMemcacheKeyValueCache' => 'PhutilKeyValueCache', + 'PhutilMercurialBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilMethodNotImplementedException' => 'Exception', 'PhutilMetricsChannel' => 'PhutilChannelChannel', 'PhutilMissingSymbolException' => 'Exception', @@ -955,6 +967,7 @@ 'Phobject', 'Iterator', ), + 'PhutilPygmentizeBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilPygmentizeParser' => 'Phobject', 'PhutilPygmentizeParserTestCase' => 'PhutilTestCase', 'PhutilPygmentsSyntaxHighlighter' => 'Phobject', @@ -1028,6 +1041,7 @@ 'Phobject', 'Iterator', ), + 'PhutilSubversionBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilSyntaxHighlighter' => 'Phobject', 'PhutilSyntaxHighlighterEngine' => 'Phobject', 'PhutilSyntaxHighlighterException' => 'Exception', diff --git a/src/filesystem/binary/PhutilBinaryAnalyzer.php b/src/filesystem/binary/PhutilBinaryAnalyzer.php new file mode 100644 --- /dev/null +++ b/src/filesystem/binary/PhutilBinaryAnalyzer.php @@ -0,0 +1,65 @@ +getBinaryKey(); + } + + public function getBinaryKey() { + return $this->getPhobjectClassConstant('BINARY'); + } + + public function isBinaryAvailable() { + return Filesystem::binaryExists($this->getBinaryName()); + } + + abstract protected function newBinaryVersion(); + + protected function newBinaryPath() { + return Filesystem::resolveBinary($this->getBinaryName()); + } + + final public function getBinaryVersion() { + return $this->newBinaryVersion(); + } + + final public function requireBinaryVersion() { + $version = $this->getBinaryVersion(); + if ($version === null) { + throw new Exception( + pht( + 'Unable to determine the installed version of binary "%s". This '. + 'version is required.')); + } + return $version; + } + + final public function getBinaryPath() { + return $this->newBinaryPath(); + } + + final public static function getAllBinaries() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getBinaryKey') + ->setSortMethod('getBinaryName') + ->execute(); + } + + final public static function getForBinary($binary) { + $map = self::getAllBinaries(); + + $analyzer = idx($map, $binary); + if (!$analyzer) { + throw new Exception( + pht( + 'No analyzer is available for binary "%s".', + $binary)); + } + + return $analyzer; + } + +} diff --git a/src/filesystem/binary/PhutilDiffBinaryAnalyzer.php b/src/filesystem/binary/PhutilDiffBinaryAnalyzer.php new file mode 100644 --- /dev/null +++ b/src/filesystem/binary/PhutilDiffBinaryAnalyzer.php @@ -0,0 +1,31 @@ +requireBinaryVersion(), + self::CAPABILITY_FILES); + } + + public function isMercurialVulnerableToInjection() { + return self::versionHasCapability( + $this->requireBinaryVersion(), + self::CAPABILITY_INJECTION); + } + + + public static function versionHasCapability( + $mercurial_version, + $capability) { + + switch ($capability) { + case self::CAPABILITY_FILES: + return version_compare($mercurial_version, '3.2', '>='); + case self::CAPABILITY_INJECTION: + return version_compare($mercurial_version, '3.2.4', '<'); + default: + throw new Exception( + pht( + 'Unknown Mercurial capability "%s".', + $capability)); + } + + } + + +} diff --git a/src/filesystem/binary/PhutilPygmentizeBinaryAnalyzer.php b/src/filesystem/binary/PhutilPygmentizeBinaryAnalyzer.php new file mode 100644 --- /dev/null +++ b/src/filesystem/binary/PhutilPygmentizeBinaryAnalyzer.php @@ -0,0 +1,31 @@ + '2.11.0', + 'definitely git 7.0' => null, + ); + + foreach ($map as $input => $expect) { + $actual = PhutilGitBinaryAnalyzer::parseGitBinaryVersion($input); + $this->assertEqual($expect, $actual, $input); + } + } + + public function getParseMercurialBinaryVersions() { + $map = array( + "Mercurial Distributed SCM (version 3.5.2+20151001)\n" + => '3.5.2', + 'This Is Mercurial 22.0' => null, + ); + + foreach ($map as $input => $expect) { + $actual = + PhutilMercurialBinaryAnalyzer::parseMercurialBinaryVersion( + $input); + $this->assertEqual($expect, $actual, $input); + } + } + + public function testParseSubversionBinaryVersions() { + $map = array( + "1.7.20\n" => '1.7.20', + ); + + foreach ($map as $input => $expect) { + $actual = + PhutilSubversionBinaryAnalyzer::parseSubversionBinaryVersion( + $input); + $this->assertEqual($expect, $actual, $input); + } + } + + public function testParseDiffBinaryVersions() { + $diff_version_281 = << '2.8.1', + 'diff version 1.2.3' => null, + ); + + foreach ($map as $input => $expect) { + $actual = PhutilDiffBinaryAnalyzer::parseDiffBinaryVersion($input); + $this->assertEqual($expect, $actual, $input); + } + } + + public function testParsePygmentizeBinaryVersions() { + $map = array( + "Pygments version 2.0.1, (c) 2006-2014 by Georg Brandl.\n" + => '2.0.1', + 'pygments 3.4' => null, + ); + + foreach ($map as $input => $expect) { + $actual = + PhutilPygmentizeBinaryAnalyzer::parsePygmentizeBinaryVersion( + $input); + $this->assertEqual($expect, $actual, $input); + } + } + + public function testMercurialFilesCommandVersions() { + $cases = array( + PhutilMercurialBinaryAnalyzer::CAPABILITY_FILES => array( + '2.6.2' => false, + '2.9' => false, + '3.1' => false, + '3.2' => true, + '3.3' => true, + '3.5.2' => true, + ), + PhutilMercurialBinaryAnalyzer::CAPABILITY_INJECTION => array( + '2.0' => true, + '3.2.3' => true, + '3.2.4' => false, + ), + ); + + foreach ($cases as $capability => $map) { + foreach ($map as $input => $expect) { + $actual = PhutilMercurialBinaryAnalyzer::versionHasCapability( + $input, + $capability); + $this->assertEqual( + $expect, + $actual, + pht('%s on %s', $capability, $input)); + } + } + + } + +}