primo commit
This commit is contained in:
		
							
								
								
									
										516
									
								
								libraries/vendor/joomla/filesystem/src/Patcher.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										516
									
								
								libraries/vendor/joomla/filesystem/src/Patcher.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,516 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * Part of the Joomla Framework Filesystem Package | ||||
|  * | ||||
|  * @copyright  Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. | ||||
|  * @license    GNU General Public License version 2 or later; see LICENSE | ||||
|  */ | ||||
|  | ||||
| namespace Joomla\Filesystem; | ||||
|  | ||||
| /** | ||||
|  * A Unified Diff Format Patcher class | ||||
|  * | ||||
|  * @link   http://sourceforge.net/projects/phppatcher/ This has been derived from the PhpPatcher version 0.1.1 written by Giuseppe Mazzotta | ||||
|  * @since  1.0 | ||||
|  */ | ||||
| class Patcher | ||||
| { | ||||
|     /** | ||||
|      * Regular expression for searching source files | ||||
|      * | ||||
|      * @var    string | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     public const SRC_FILE = '/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A'; | ||||
|  | ||||
|     /** | ||||
|      * Regular expression for searching destination files | ||||
|      * | ||||
|      * @var    string | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     public const DST_FILE = '/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A'; | ||||
|  | ||||
|     /** | ||||
|      * Regular expression for searching hunks of differences | ||||
|      * | ||||
|      * @var    string | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     public const HUNK = '/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A'; | ||||
|  | ||||
|     /** | ||||
|      * Regular expression for splitting lines | ||||
|      * | ||||
|      * @var    string | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     public const SPLIT = '/(\r\n)|(\r)|(\n)/'; | ||||
|  | ||||
|     /** | ||||
|      * Source files | ||||
|      * | ||||
|      * @var    array | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     protected $sources = []; | ||||
|  | ||||
|     /** | ||||
|      * Destination files | ||||
|      * | ||||
|      * @var    array | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     protected $destinations = []; | ||||
|  | ||||
|     /** | ||||
|      * Removal files | ||||
|      * | ||||
|      * @var    array | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     protected $removals = []; | ||||
|  | ||||
|     /** | ||||
|      * Patches | ||||
|      * | ||||
|      * @var    array | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     protected $patches = []; | ||||
|  | ||||
|     /** | ||||
|      * Singleton instance of this class | ||||
|      * | ||||
|      * @var    Patcher | ||||
|      * @since  1.0 | ||||
|      */ | ||||
|     protected static $instance; | ||||
|  | ||||
|     /** | ||||
|      * Constructor | ||||
|      * | ||||
|      * The constructor is protected to force the use of Patcher::getInstance() | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     protected function __construct() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method to get a patcher | ||||
|      * | ||||
|      * @return  Patcher  an instance of the patcher | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     public static function getInstance() | ||||
|     { | ||||
|         if (!isset(static::$instance)) { | ||||
|             static::$instance = new static(); | ||||
|         } | ||||
|  | ||||
|         return static::$instance; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Reset the pacher | ||||
|      * | ||||
|      * @return  Patcher  This object for chaining | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     public function reset() | ||||
|     { | ||||
|         $this->sources      = []; | ||||
|         $this->destinations = []; | ||||
|         $this->removals     = []; | ||||
|         $this->patches      = []; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Apply the patches | ||||
|      * | ||||
|      * @return  integer  The number of files patched | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      * @throws  \RuntimeException | ||||
|      */ | ||||
|     public function apply() | ||||
|     { | ||||
|         foreach ($this->patches as $patch) { | ||||
|             // Separate the input into lines | ||||
|             $lines = self::splitLines($patch['udiff']); | ||||
|  | ||||
|             // Loop for each header | ||||
|             while (self::findHeader($lines, $src, $dst)) { | ||||
|                 $done = false; | ||||
|  | ||||
|                 if ($patch['strip'] === null) { | ||||
|                     $src = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $src); | ||||
|                     $dst = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $dst); | ||||
|                 } else { | ||||
|                     $src = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $src); | ||||
|                     $dst = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $dst); | ||||
|                 } | ||||
|  | ||||
|                 // Loop for each hunk of differences | ||||
|                 while (self::findHunk($lines, $srcLine, $srcSize, $dstLine, $dstSize)) { | ||||
|                     $done = true; | ||||
|  | ||||
|                     // Apply the hunk of differences | ||||
|                     $this->applyHunk($lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize); | ||||
|                 } | ||||
|  | ||||
|                 // If no modifications were found, throw an exception | ||||
|                 if (!$done) { | ||||
|                     throw new \RuntimeException('Invalid Diff'); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Initialize the counter | ||||
|         $done = 0; | ||||
|  | ||||
|         // Patch each destination file | ||||
|         foreach ($this->destinations as $file => $content) { | ||||
|             $content = implode("\n", $content); | ||||
|  | ||||
|             if (File::write($file, $content)) { | ||||
|                 if (isset($this->sources[$file])) { | ||||
|                     $this->sources[$file] = $content; | ||||
|                 } | ||||
|  | ||||
|                 $done++; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Remove each removed file | ||||
|         foreach ($this->removals as $file) { | ||||
|             if (File::delete($file)) { | ||||
|                 if (isset($this->sources[$file])) { | ||||
|                     unset($this->sources[$file]); | ||||
|                 } | ||||
|  | ||||
|                 $done++; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Clear the destinations cache | ||||
|         $this->destinations = []; | ||||
|  | ||||
|         // Clear the removals | ||||
|         $this->removals = []; | ||||
|  | ||||
|         // Clear the patches | ||||
|         $this->patches = []; | ||||
|  | ||||
|         return $done; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Add a unified diff file to the patcher | ||||
|      * | ||||
|      * @param   string   $filename  Path to the unified diff file | ||||
|      * @param   string   $root      The files root path | ||||
|      * @param   integer  $strip     The number of '/' to strip | ||||
|      * | ||||
|      * @return  Patcher  $this for chaining | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     public function addFile($filename, $root, $strip = 0) | ||||
|     { | ||||
|         return $this->add(file_get_contents($filename), $root, $strip); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Add a unified diff string to the patcher | ||||
|      * | ||||
|      * @param   string   $udiff  Unified diff input string | ||||
|      * @param   string   $root   The files root path | ||||
|      * @param   integer  $strip  The number of '/' to strip | ||||
|      * | ||||
|      * @return  Patcher  $this for chaining | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     public function add($udiff, $root, $strip = 0) | ||||
|     { | ||||
|         $this->patches[] = [ | ||||
|             'udiff' => $udiff, | ||||
|             'root'  => isset($root) ? rtrim($root, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR : '', | ||||
|             'strip' => $strip, | ||||
|         ]; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Separate CR or CRLF lines | ||||
|      * | ||||
|      * @param   string  $data  Input string | ||||
|      * | ||||
|      * @return  array  The lines of the input destination file | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     protected static function splitLines($data) | ||||
|     { | ||||
|         return preg_split(self::SPLIT, $data); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Find the diff header | ||||
|      * | ||||
|      * The internal array pointer of $lines is on the next line after the finding | ||||
|      * | ||||
|      * @param   array   $lines  The udiff array of lines | ||||
|      * @param   string  $src    The source file | ||||
|      * @param   string  $dst    The destination file | ||||
|      * | ||||
|      * @return  boolean  TRUE in case of success, FALSE in case of failure | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      * @throws  \RuntimeException | ||||
|      */ | ||||
|     protected static function findHeader(&$lines, &$src, &$dst) | ||||
|     { | ||||
|         // Get the current line | ||||
|         $line = current($lines); | ||||
|  | ||||
|         // Search for the header | ||||
|         while ($line !== false && !preg_match(self::SRC_FILE, $line, $m)) { | ||||
|             $line = next($lines); | ||||
|         } | ||||
|  | ||||
|         if ($line === false) { | ||||
|             // No header found, return false | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Set the source file | ||||
|         $src = $m[1]; | ||||
|  | ||||
|         // Advance to the next line | ||||
|         $line = next($lines); | ||||
|  | ||||
|         if ($line === false) { | ||||
|             throw new \RuntimeException('Unexpected EOF'); | ||||
|         } | ||||
|  | ||||
|         // Search the destination file | ||||
|         if (!preg_match(self::DST_FILE, $line, $m)) { | ||||
|             throw new \RuntimeException('Invalid Diff file'); | ||||
|         } | ||||
|  | ||||
|         // Set the destination file | ||||
|         $dst = $m[1]; | ||||
|  | ||||
|         // Advance to the next line | ||||
|         if (next($lines) === false) { | ||||
|             throw new \RuntimeException('Unexpected EOF'); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Find the next hunk of difference | ||||
|      * | ||||
|      * The internal array pointer of $lines is on the next line after the finding | ||||
|      * | ||||
|      * @param   array   $lines    The udiff array of lines | ||||
|      * @param   string  $srcLine  The beginning of the patch for the source file | ||||
|      * @param   string  $srcSize  The size of the patch for the source file | ||||
|      * @param   string  $dstLine  The beginning of the patch for the destination file | ||||
|      * @param   string  $dstSize  The size of the patch for the destination file | ||||
|      * | ||||
|      * @return  boolean  TRUE in case of success, false in case of failure | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      * @throws  \RuntimeException | ||||
|      */ | ||||
|     protected static function findHunk(&$lines, &$srcLine, &$srcSize, &$dstLine, &$dstSize) | ||||
|     { | ||||
|         $line = current($lines); | ||||
|  | ||||
|         if (preg_match(self::HUNK, $line, $m)) { | ||||
|             $srcLine = (int) $m[1]; | ||||
|  | ||||
|             if ($m[3] === '') { | ||||
|                 $srcSize = 1; | ||||
|             } else { | ||||
|                 $srcSize = (int) $m[3]; | ||||
|             } | ||||
|  | ||||
|             $dstLine = (int) $m[4]; | ||||
|  | ||||
|             if ($m[6] === '') { | ||||
|                 $dstSize = 1; | ||||
|             } else { | ||||
|                 $dstSize = (int) $m[6]; | ||||
|             } | ||||
|  | ||||
|             if (next($lines) === false) { | ||||
|                 throw new \RuntimeException('Unexpected EOF'); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Apply the patch | ||||
|      * | ||||
|      * @param   array   $lines    The udiff array of lines | ||||
|      * @param   string  $src      The source file | ||||
|      * @param   string  $dst      The destination file | ||||
|      * @param   string  $srcLine  The beginning of the patch for the source file | ||||
|      * @param   string  $srcSize  The size of the patch for the source file | ||||
|      * @param   string  $dstLine  The beginning of the patch for the destination file | ||||
|      * @param   string  $dstSize  The size of the patch for the destination file | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      * @throws  \RuntimeException | ||||
|      */ | ||||
|     protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize) | ||||
|     { | ||||
|         $srcLine--; | ||||
|         $dstLine--; | ||||
|         $line = current($lines); | ||||
|  | ||||
|         // Source lines (old file) | ||||
|         $source = []; | ||||
|  | ||||
|         // New lines (new file) | ||||
|         $destin  = []; | ||||
|         $srcLeft = $srcSize; | ||||
|         $dstLeft = $dstSize; | ||||
|  | ||||
|         do { | ||||
|             if (!isset($line[0])) { | ||||
|                 $source[] = ''; | ||||
|                 $destin[] = ''; | ||||
|                 $srcLeft--; | ||||
|                 $dstLeft--; | ||||
|             } elseif ($line[0] == '-') { | ||||
|                 if ($srcLeft == 0) { | ||||
|                     throw new \RuntimeException('Unexpected remove line at line ' . key($lines)); | ||||
|                 } | ||||
|  | ||||
|                 $source[] = substr($line, 1); | ||||
|                 $srcLeft--; | ||||
|             } elseif ($line[0] == '+') { | ||||
|                 if ($dstLeft == 0) { | ||||
|                     throw new \RuntimeException('Unexpected add line at line ' . key($lines)); | ||||
|                 } | ||||
|  | ||||
|                 $destin[] = substr($line, 1); | ||||
|                 $dstLeft--; | ||||
|             } elseif ($line != '\\ No newline at end of file') { | ||||
|                 $line     = substr($line, 1); | ||||
|                 $source[] = $line; | ||||
|                 $destin[] = $line; | ||||
|                 $srcLeft--; | ||||
|                 $dstLeft--; | ||||
|             } | ||||
|  | ||||
|             if ($srcLeft == 0 && $dstLeft == 0) { | ||||
|                 // Now apply the patch, finally! | ||||
|                 if ($srcSize > 0) { | ||||
|                     $srcLines = & $this->getSource($src); | ||||
|  | ||||
|                     if (!isset($srcLines)) { | ||||
|                         throw new \RuntimeException( | ||||
|                             'Unexisting source file: ' . Path::removeRoot($src) | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if ($dstSize > 0) { | ||||
|                     if ($srcSize > 0) { | ||||
|                         $dstLines  = & $this->getDestination($dst, $src); | ||||
|                         $srcBottom = $srcLine + \count($source); | ||||
|  | ||||
|                         for ($l = $srcLine; $l < $srcBottom; $l++) { | ||||
|                             if ($srcLines[$l] != $source[$l - $srcLine]) { | ||||
|                                 throw new \RuntimeException( | ||||
|                                     sprintf( | ||||
|                                         'Failed source verification of file %1$s at line %2$s', | ||||
|                                         Path::removeRoot($src), | ||||
|                                         $l | ||||
|                                     ) | ||||
|                                 ); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         array_splice($dstLines, $dstLine, \count($source), $destin); | ||||
|                     } else { | ||||
|                         $this->destinations[$dst] = $destin; | ||||
|                     } | ||||
|                 } else { | ||||
|                     $this->removals[] = $src; | ||||
|                 } | ||||
|  | ||||
|                 next($lines); | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             $line = next($lines); | ||||
|         } while ($line !== false); | ||||
|  | ||||
|         throw new \RuntimeException('Unexpected EOF'); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the lines of a source file | ||||
|      * | ||||
|      * @param   string  $src  The path of a file | ||||
|      * | ||||
|      * @return  array  The lines of the source file | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     protected function &getSource($src) | ||||
|     { | ||||
|         if (!isset($this->sources[$src])) { | ||||
|             if (is_readable($src)) { | ||||
|                 $this->sources[$src] = self::splitLines(file_get_contents($src)); | ||||
|             } else { | ||||
|                 $this->sources[$src] = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $this->sources[$src]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the lines of a destination file | ||||
|      * | ||||
|      * @param   string  $dst  The path of a destination file | ||||
|      * @param   string  $src  The path of a source file | ||||
|      * | ||||
|      * @return  array  The lines of the destination file | ||||
|      * | ||||
|      * @since   1.0 | ||||
|      */ | ||||
|     protected function &getDestination($dst, $src) | ||||
|     { | ||||
|         if (!isset($this->destinations[$dst])) { | ||||
|             $this->destinations[$dst] = $this->getSource($src); | ||||
|         } | ||||
|  | ||||
|         return $this->destinations[$dst]; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user