WikiDebug/show source

From TestWiki

Jump to: navigation, search

The WikiDebug extension adds a new tag that allows you to display the contents of a php file within the page. For security reasons it only allows the display of files that are added to an array of allowed files. If an extension that adds a hook for the 'php' tag has been installed (e.g. the GeSHi Syntax Highlighter) then this will be used to wrap the output, otherwise 'pre' tags will be used.

[edit] Syntax

<show_source file="{FileName}"></show_source>
  • {FileName} is the name of a file residing on the server.

As a security measure this extension will only allow display of tables added to the $wgWikiDebug_ViewableFiles array. Any other files are blocked.

For example, to view the file containing this extension, you would need to add the following to LocalSettings.php:

$wgWikiDebug_ViewableFiles[] = "WikiDebug.php";

Note that paths are relative to the current include path, which includes the extensions directory on this wiki, but which may not on yours.

[edit] Example 1: WikiDebug.php

Here is the current (live) contents of WikiDebug.php - the file that contains this extension.

<show_source file="WikiDebug.php"></show_source>

Live sourcecode viewer: WikiDebug.php
Last modified: 2012-01-08 10:41:11

<?php
if (!defined('MEDIAWIKI')) die("MediaWiki extensions cannot be run directly.");
/**
 * An extension to implement various useful debugging tools.
 *
 * @package MediaWiki
 * @subpackage Extensions
 *
 * @author Mark Clements <mclements at kennel17 dot co dot uk>
 * @copyright copyright © 2006-2012, Mark Clements
 * @license http://creativecommons.org/licenses/by-sa/2.5/ cc-by-sa 2.5 or later
 * @version $Rev: 661 $
 */
 // Setup version number
	$pMCExt_Version = '$Rev: 661 $';
	$pMCExt_Version = substr($pMCExt_Version, 6, -2);
// Setup extension credits
	$wgExtensionCredits['other'][] = array(
		'name' => 'WikiDebug',
		'version' => "r" . $pMCExt_Version,
		'author' => 'Mark Clements',
		'description' => "a set of debugging tools for extension developers",
		'url' => 'http://www.kennel17.co.uk/testwiki/Debugging%20tools',
	);
// Tidy up
	unset($pMCExt_Version);
 
	if (!isset($wgWikiDebug_ViewableTables))
		$wgWikiDebug_ViewableTables = array();
	if (!isset($wgWikiDebug_ViewableFiles))
		$wgWikiDebug_ViewableFiles = array();
 
	$pWikiDebug_Messages = array(
	// Messages for wfWikiDebug_DescribeTable()
		'debug_table_header' => "Live Table Definition: $1",
		'debug_no_table' => "No table specified.",
		'debug_table_not_found' => "Table cannot be viewed.  It either does not exist, or is protected against viewing.",
		'debug_no_fields' => "No fields found.",
		'debug_index_header' => "Indexes",
		'debug_no_indexes' => "No indexes defined.",
		'debug_error_reading_tabledef' => "There was an error reading the table definition!",
 
	// Messages for wfWikiDebug_ShowSource() and wfWikiDebug_ShowVersion()
		'debug_file_header' => "Live sourcecode viewer: $1",
		'debug_last_modified' => "Last modified: $1",
		'debug_no_file' => "No file specified.",
		'debug_file_blocked' => "File cannot be viewed.  It either does not exist, or is protected against viewing.",
		'debug_file_blocked_short' => "File not found",
		'debug_file_unreadable' => "File cannot be read.",
		'debug_no_version' => "version unknown",
		'debug_version' => "rev \$1",
	);
 
	$wgExtensionFunctions[] = "wfWikiDebug";
	function wfWikiDebug() {
		global $wgMessageCache, $wgParser;
		global $pWikiDebug_Messages, $wgWikiDebug_ViewableFiles;
 
		$wgMessageCache->addMessages($pWikiDebug_Messages);
 
	// Specific tags to be parsed	
		$wgParser->setHook( "describe_table", "wfWikiDebug_DescribeTable" );
		$wgParser->setHook( "show_source", "wfWikiDebug_ShowSource" );
		$wgParser->setHook( "show_version", "wfWikiDebug_ShowVersion" );
 
	// Tidy all paths in the list of viewable files, to ensure that directory
	// separators are treated in a system-agnostic fashion.
		foreach ($wgWikiDebug_ViewableFiles as $Key => $Path)
			$wgWikiDebug_ViewableFiles[$Key] = pwfWikiDebug_TidyPath($Path);
	}
 
	function wfWikiDebug_DescribeTable($Input, $Args) {
		global $wgParser;
		global $wgWikiDebug_ViewableTables;
 
		$wgParser->disableCache();
 
		if (isset($Args['table']))
			$TableName = $Args['table'];
		else
			$TableName = "";
		$Output = "<h3>" . wfMsg('debug_table_header', $TableName) . "</h3>\n";
 
		if ($TableName == "")
			$Output .= wfMsg('debug_no_table');
 
		elseif (!in_array($TableName, $wgWikiDebug_ViewableTables))
			$Output .= wfMsg('debug_table_not_found');
 
		else {
 
			$DB =& wfGetDB(DB_MASTER);
 
			if ($DB->tableExists($TableName)) {
 
			// Get the requested display mode.
				$Display = "sql";
				if (isset($Args['display']))
					$Display = strtolower(trim($Args['display']));
 
			// Output the data according to the display mode.
				switch ($Display) {
					case "tables":
					case "table":
					// Get field data from table
						$Result = $DB->query("DESCRIBE " . $DB->tableName($TableName) . ";");
						$Fields = array();
						while ( $row = $DB->fetchObject( $Result ) )
							$Fields[] = $row;
 
					// If required, get index data from table
						$ShowIndexes = true;
						if (isset($Args['showindexes'])) {
							switch (strtolower(trim($Args['showindexes']))) {
								case "0":
								case "false":
									$ShowIndexes = false;
							}
						}
						$Indexes = array();
						if ($ShowIndexes) {
							$Result = $DB->query("SHOW INDEX FROM " . $DB->tableName($Args['table']) . ";");
							while ( $row = $DB->fetchObject( $Result ) )
								$Indexes[] = $row;
						}
 
						if (count($Fields) == 0)
							$Output .= wfMsg('debug_no_fields');
 
						else {
							$Output .= '<table border="1" cellpadding="5" cellspacing="2"><tr>';
							foreach ($Fields[0] as $RowHeader => $Row)
								$Output .= '<th>' . $RowHeader . '</th>';
							foreach ($Fields as $Row) {
								$Output .= '<tr>';
								foreach ($Row as $RowValue) {
									if ($RowValue === NULL || $RowValue == "")
										$RowValue = "&nbsp;";
									$Output .= '<td>' . $RowValue . '</td>';
								}
								$Output .= '</tr>';
							}
							$Output .= '</table>';
 
							if ($ShowIndexes) {
								$Output .= "<h3>" . wfMsg('debug_index_header') . "</h3>\n";
 
								if (count($Indexes) > 0) {
									$Output .= '<table border="1" cellpadding="5" cellspacing="2"><tr>';
									foreach ($Indexes[0] as $RowHeader => $Row) {
										if ($RowHeader != "Table")
											$Output .= '<th>' . $RowHeader . '</th>';
									}
									foreach ($Indexes as $Row) {
										$Output .= '<tr>';
										foreach ($Row as $RowHeader => $RowValue) {
											if ($RowHeader != "Table") {
												if ($RowValue === NULL || $RowValue == "")
													$RowValue = "&nbsp;";
												$Output .= '<td>' . $RowValue . '</td>';
											}
										}
										$Output .= '</tr>';
									}
									$Output .= '</table>';
								}
								else
									$Output .= "&nbsp;&nbsp;&nbsp;" . wfMsg('debug_no_indexes');
							}
						}
 
						break;
 
					case "sql":
					default:
					// Get field data from table
						$Result = $DB->query("SHOW CREATE TABLE " . $DB->tableName($TableName) . ";");
						if ( $Result !== false && $row = $DB->fetchObject( $Result ) ) {
							$SQL = "";
							foreach ( $row as $key => $val ) {
								if ($key = "Create Table")
									$SQL = htmlspecialchars($val);
							}
 
							$Removables = array(
								"/character set .* /Ui",
								"/collate .* /Ui",
								"/ ?DEFAULT CHARSET=.*( |$)/Ui",
								"/ ?COLLATE=.*( |$)/Ui",
								"/ ?AUTO_INCREMENT\s*=\s*([0-9])+/",
							);
							$SQL = preg_replace($Removables, "", $SQL);
 
							$Search = array(
								"/^CREATE TABLE " . $DB->tableName($TableName) . "/i",
								"/ENGINE=(.*)( |$)/Ui",
								"/^/m",		// Space at the start of the line, to ensure <pre> block works properly.
								"/([^;])$/",
							);
							$Replace = array(
								"CREATE TABLE IF NOT EXISTS `" . $TableName . "`",
								"TYPE=$1$2",
								" ",
								"$1;",
							);
							$SQL = preg_replace($Search, $Replace, $SQL);
							$Output .= $SQL . "\n";
						}
 
						else
							$Output .= wfMsg('debug_error_reading_tabledef');
				}
			}
 
			else
				$Output .= wfMsg('debug_table_not_found');
		}
 
		$Output = '<div style="border: 1px solid #AAAAAA; background-color: #EEEEEE; padding: 1em;">' 
				. $Output . '</div>';
		return $Output;
	}
 
	function wfWikiDebug_ShowSource($Input, $Args) {
		global $wgOut, $wgParser;
 
		$wgParser->disableCache();
		$AllowPHPOutput = false;
 
		if (isset($Args['file']))
			$FileName = $Args['file'];
		else
			$FileName = "";
		$Header = wfMsg('debug_file_header', $FileName);
 
		if ($FileName == "")
			$Output = wfMsg('debug_no_file');
 
		else {
			$FilePath = pwfWikiDebug_GetRealPath($FileName);
			if (!$FilePath) 
				$Output = wfMsg('debug_file_blocked');
 
			elseif (($File = @fopen($FilePath, "r", true)) === false)
				$Output = wfMsg('debug_file_unreadable');
 
			else {
			// Get last modified date
				$ModDate = date("Y-m-d h:i:s", filemtime($FilePath));
				$Header .= "<br><span style=\"font-size: 80%; font-weight: normal;\">" 
						 . wfMsg('debug_last_modified', $ModDate) . "</span>";
 
			// Get length of file.
				fseek($File, 0, SEEK_END); 
				$Length = ftell($File);
				fseek($File, 0, SEEK_SET);
 
			// Get contents of file.
				$Output = fread($File, $Length);
				$AllowPHPOutput = true;
			}
		}
 
		$Header = "<h3>" . $Header . "</h3>\n";
 
		if ($AllowPHPOutput && isset($wgParser->mTagHooks['php'])) {
		// This can easily use more than the standard available memory, so remove limit.
			$MemLimit = ini_get('memory_limit');
			ini_set('memory_limit', -1);
			$Output = $Header . call_user_func($wgParser->mTagHooks['php'], $Output, array());
			if (preg_match_all('/copyright (.) /i', $Output, $Matches)) {
				$Matches = array_unique($Matches[1]);
				foreach ($Matches as $CopyrightSymbol) {
					$Output = preg_replace('/copyright (' . $CopyrightSymbol . ') /ie', 
										   '"copyright " . htmlentities("$1") . " "', $Output);
				}
			}
			ini_set('memory_limit', $MemLimit);
		}
		else
			$Output = "<pre>" . $Header . htmlspecialchars($Output) . "</pre>";
 
	// Some special code to replace the literal copyright symbol with the HTML
	// &copy; entity, as this might otherwise be displayed as a question-mark, due to 
	// encoding issues.
		$Output = str_replace("©", "&copy;", $Output);
 
		return $Output;
	}
 
	function wfWikiDebug_ShowVersion($Input, $Args) {
		global $wgOut, $wgParser;
 
		$wgParser->disableCache();
 
		if (isset($Args['file']))
			$FileName = $Args['file'];
		else
			$FileName = "";
 
		if ($FileName == "")
			$Output = wfMsg('debug_no_file');
 
		else {
			$FilePath = pwfWikiDebug_GetRealPath($FileName);
			if (!$FilePath) 
				$Output = wfMsg('debug_file_blocked');
 
			elseif (($File = @fopen($FilePath, "r", true)) === false)
				$Output = wfMsg('debug_file_unreadable');
 
			else {
			// Get length of file.
				fseek($File, 0, SEEK_END); 
				$Length = ftell($File);
			    fseek($File, 0, SEEK_SET);
 
			// Get contents of file.
				$Contents = fread($File, $Length);
				$VersionRegex = '/(?:\$Rev:|\$LastChangedRevision:|\$Revision:|) ?([0-9]+) ?\$/Ui';
				$Result = preg_match($VersionRegex, $Contents, $matches);
				if (count($matches) > 0)
					$Output = wfMsg('debug_version', $matches[1]);
				else
					$Output = wfMsg('debug_no_version');
			}
		}
 
		return "<i>(" . $Output . ")</i>";
	}
 
/////////////////////////// PARSER FUNCTIONS ///////////////////////////
// MW 1.7 and above, only.
 
// SETUP FUNCTIONS
 
	$wgHooks['ParserFirstCallInit'][] = 'wfWikiDebug_SetupParserFunctions';
	$wgHooks['LanguageGetMagic'][]	  = 'wfWikiDebug_SetupLanguageMagic';
 
	function wfWikiDebug_SetupParserFunctions(&$Parser) {
		$Parser->setFunctionHook('IfExtensionPresent', 
								 'wfWikiDB_IfExtensionPresent');
        return true;
	}
 
	function wfWikiDebug_SetupLanguageMagic(&$MagicWords, $langCode) {
	// Add the magic word.
	// Set first array element to 0 to indicate non-case-sensitivity.
	// Additional elements are synonyms.
		$MagicWords['IfExtensionPresent'] = array(1, 'IfExtensionPresent');
 
	// Return true, so other parser hooks execute properly.
		return true;
	}
 
// INDIVIDUAL PARSER FUNCTIONS
 
	function wfWikiDB_IfExtensionPresent($Parser, $Param1="", $Param2="", 
										 $Param3="")
	{
		global $wgExtensionCredits;
 
		$Matched = false;
		foreach ($wgExtensionCredits as $Type => $Extensions) {
			foreach ($Extensions as $Extension) {
				if (isset($Extension['name']) && $Extension['name'] == $Param1) {
					$Matched = true;
					break 2;
				}
			}
		}
 
		if ($Matched)
			return $Param2;
		else
			return $Param3;
	}
 
/////////////////////////// SUPPORT FUNCTIONS ///////////////////////////
 
// pwfWikiDebug_GetRealPath()
// Takes a $FileName argument, and checks that (a) the file exists in the 
// include paths and (b) it is in the list of viewable files.
// If it fails either of these tests, the function returns false, indicating that
// the file cannot be viewed (there is deliberately no distinction between the 
// different reasons why it may not be viewable).
// If it succeeds, and the file exists and is on the white list, then the real
// path to the file is returned.
// The function does not check whether the file is readable by the web user.
	function pwfWikiDebug_GetRealPath($FileName) {
		$FileName = pwfWikiDebug_TidyPath($FileName);
 
	// First, check that the file exists in the include path.  We do this first
	// as it will allow us to use realpath() in the CanViewFile() function if we 
	// need to (currently it isn't used, but at one point it was thought to be
	// required and I'm not sure that I won't change my mind again in the future).
		$FilePath = pwfWikiDebug_file_exists_incpath($FileName);
		if (!$FilePath)
			return false;
 
	// If file exists, check it is viewable.
		if (!pwfWikiDebug_CanViewFile($FileName))
			return false;
 
		return $FilePath;
	}
 
// pwfWikiDebug_file_exists_incpath()
// For relative paths, checks if the file exists in the include path and returns 
// the full path of where it was found if it does, or false if not.
// For absolute paths, it doesn't check the include path - it just returns the 
// supplied $File path if it exists, or false if not.
// Function was modified from file_exists_incpath() - credits follow:
/**
 * Check if a file exists in the include path
 *
 * @version     1.2.1
 * @author      Aidan Lister <aidan@php.net>
 * @link        http://aidanlister.com/repos/v/function.file_exists_incpath.php
 * @param       string     $File       Name of the file to look for
 * @return      mixed      The full path if file exists, FALSE if it does not
 */
	function pwfWikiDebug_file_exists_incpath($File) {
 
	// If this is an absolute path, then we don't need to resolve it in terms
	// of the include path - just check whether the file exists.
		if (in_array(substr($File, 0, 1), array("\\", "/"))) {
			if (file_exists($File))
				return $File;
		}
 
	// Otherwise it is a relative path, so look for it in each of the include 
	// directories, selecting the first directory in which the file is found.
		else {
			$arrPaths = explode(PATH_SEPARATOR, ini_get('include_path'));
			foreach ($arrPaths as $Path) {
			// Skip over any blank paths.
				if ($Path != "") {
				// Formulate the absolute path, and if the file exists there,
				// return it.
					$FullPath = $Path . "/" . $File;
					if (file_exists($FullPath))
						return $FullPath;
				}
			}
		}
 
	// If the file was not found in the path, return false.
		return false;
	}
 
// pwfWikiDebug_CanViewFile()
// Returns true if the named file is viewable according to the settings in 
// $wgWikiDebug_ViewableFiles.  This is an array of files/directories.  If the
// entry is a directory, all of it's contents, including sub-directories, are 
// viewable.
	function pwfWikiDebug_CanViewFile($FileName) {
		global $wgWikiDebug_ViewableFiles;
 
	// If the supplied filename is in the list of viewable files, then return
	// true.
		if (in_array($FileName, $wgWikiDebug_ViewableFiles))
			return true;
 
	// Otherwise, if there are any directories in the list of viewable files,
	// return true if this file is within one of those directories.
		foreach ($wgWikiDebug_ViewableFiles as $ViewableFile) {
		// Ensure the path ends in a directory separator.  This doesn't cause
		// any problems if the file is not a directory, as we separately test
		// that the full path exists.
			if (substr($ViewableFile, -1) != "/")
				$ViewableFile .= "/";
 
		// If the path of the $ViewableFile (including trailing backslash) matches
		// the start of the file we are checking permission for, then the file is
		// inside the viewable directory, so we return true.
		// TODO: We could be a bit smarter about matching files which are 
		//		 directories (i.e. allowing them to be specified with or without
		//		 a trailing slash) but this isn't required at the moment.  It needs
		//		 fixing at the point where we have any tags that can operate on
		//		 directories (e.g. <file_list> to provide a directory listing, or
		//		 <zip> tag to zip up the contents of a directory).
		//		 (Actually, as far as I can tell this is only a problem if the
		//		 ViewableFiles array contains entries with trailing slashes, as the
		//		 other way round will still result in a match - and this can be
		//		 fixed in wfWikiDebug if necessary.  Some more thought required, 
		//		 though).
			if (strpos($FileName, $ViewableFile) === 0)
				return true;
		}
 
		return false;
	}
 
// pwfWikiDebug_TidyPath()
// Converts Windows-like back-slash characters to Unix-like forward-slashes.  
// Forward slashes work on both systems, so are always safe to use, and this 
// conversion ensures that any file paths are treated in a system-agnostic way.
	function pwfWikiDebug_TidyPath($Path) {
		return str_replace("\\", "/", $Path);
	}


[edit] Example 2: Non-viewable file

Here is an attempt to view LocalSettings.php. Because this file is not in the array of allowed files, you are unable to see it's contents:

<show_source file="LocalSettings.php"></show_source>

Live sourcecode viewer: LocalSettings.php

File cannot be viewed. It either does not exist, or is protected against viewing.
Personal tools