<?php

# This file is a part of RackTables, a datacenter and server room management
# framework. See accompanying file "COPYING" for the full copyright and
# licensing information.

define
(
	'IMG_100x10_PBAR_ERROR', // 100x10, red on pink, "progr. bar error"
	'iVBORw0KGgoAAAANSUhEUgAAAGQAAAAKCAYAAABCHPt+AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A' .
	'/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sDERYTJrBhF8sAAACvSURBVEjH' .
	'7VdRDoAgCMXmQbz/qbhJfdnMQQiDTZ3vL6MHvEA03Yg3rIRSABBhV1xwMBXyp/JatFVYq7La1Hft' .
	'N709xcXxWLqE4tbGr+GXdNDqw8STxSS0z9S695ZD+e05pXhHt8RRHqtebIdoRPASM2K+ePi18Gjz' .
	'Yuwz7AKpM2cpmjPUVx3qf0OIqyLKvl+POMp6+R3Jy9oxnD4C0nsPiTrfb35viO2QiOF6foYKD57g' .
	'f1uXQb2mAAAAAElFTkSuQmCC'
);
define
(
	'IMG_100x10_PHP_GD_NA', // 100x10, red on pink: "PHP-GD is N/A"
	'iVBORw0KGgoAAAANSUhEUgAAAGQAAAAKCAYAAABCHPt+AAAAw0lEQVRIx+1XwQ3AIAjUxkHcfyo3' .
	'aV8kligino2mkvRhERHOQ/R3Src7soxcv4o2xuW3GGDBpfTW5WP+T7K1+OJSW6/lR7t/zfhzQDQg' .
	'IGwp4dJ8rkMkp2cNmgvwu0fJ4kFKQUs6ApdAo6+2Tk0HYgOeIfmGSxS32iJtLMBZmQRgSZgWVAug' .
	'VomSmDF6OfPTL905mgQDm4XgVpNS8L31vMdPa75UurRNxtaA1BLSUx7RJzoHhXeLJRYPlC1/HoYT' .
	'uy+DPGgNeJs7N+Q1AAAAAElFTkSuQmCC'
);
define
(
	'IMG_100x10_ARG_ERROR', // 100x10, red on pink: "argument error"
	'iVBORw0KGgoAAAANSUhEUgAAAGQAAAAKCAYAAABCHPt+AAAApElEQVRIx+1WUQ6AIAjF1kG8/6m4' .
	'iX2xNUYKSKnN9xc2RODxSAWxwN+RMwDiEqEesDEVzq6uI1D3tWxk5x1L30//cp/Wu+5nElM0b/G8' .
	'18FKf0GkhNbsUsIsfnnhLPfXRpbWlzY2ZyFiGTJKF2b0NWRkeTo/GpEiPZHgv8sQSRdqdksCJb3Q' .
	'xCuNIauvlr51IH269i60fq7HkJ5tbGNgQXYRTLgAuI9qHc8aWHAAAAAASUVORK5CYII='
);
define
(
	'IMG_45x90_PHP_GD_NA', // 45x90, red on pink: "ERROR: PHP-GD is not available"
	'iVBORw0KGgoAAAANSUhEUgAAAC0AAABaCAYAAAAsPd/jAAABmElEQVRo3u2aS47DIAxA24qD9P6n' .
	'4iadFVKG8T+hTKuH1AUBk4dtAja9v3p/3T6sPG4fWIAGGmiggQYaaKCB/kbo9qv2fP7t0bvcNp57' .
	'bWdlXWhPaH7Zsa61DahjPSq71T00QK9Ilhc1HTWl5QIZMEtWGafmHhnfz1giKPsom1t7gdR2rFtg' .
	'1rgm9Fgo43cWfF6AEWuOfsr4d1IIQAMNNNBAAw000ED/t2jcC7Ok8CoSqUdiPys6F8ZrKdBo8GrJ' .
	'SoGAF51P7XvcY55QNG0hajoabWci6qtlTPfwZhz1YwusGMG3t7tCdr0IE2pb/HmO9JNWJBoH+hLo' .
	'So5uO3Q2E7pQGV9wfeFpREo6Vq8qpERj0Jq177Q3uHQYmjcU7fkS6Fnbq3z9cp/ufQtsXdPVT59l' .
	'oaRPs40D/VZo55LHXRul260Vh37cIxLYajtZtL8VJ3qblNJ+Lu8R7a/1s+SNeuz/Htb5YeWG5P4L' .
	'IQsU7V+dqLGoW0gj2mnsrKa9nIhyzGUbBxpooIEGGmiggQYaaKCBBhpooAPlB5v19Mx7m4CJAAAA' .
	'AElFTkSuQmCC'
);
define
(
	'IMG_45x90_RACK_NOT_FOUND', // 45x90, red on pink: "ERROR: rack doesn't exist"
	'iVBORw0KGgoAAAANSUhEUgAAAC0AAABaCAYAAAAsPd/jAAABgElEQVRo3u2ZWxLCIAxFtcNC3P+q' .
	'upP6xQyDSQgkWq2HGT8oj56GwCXxfuz7cfuxst1+sAANNNBAAw000EADfUXo8vLk8Xjtte9yW30+' .
	'aouOHUKPBvUva+taW4Vq696xp7qHBjgqwsoXV0dtKS0XmAGzxgrzrLvHjO/PrIRj7BZabu0FUltb' .
	't8CseU3oulHqLwreb0DPatZ+wvx3UghAAw000EADDTTQQCeVlSABS39F3iMSiGrB6SgYngyCS+iT' .
	'+5dJuQsLagE4Dh3ZYIvAuT5dIeovEs2fbukerk/ELFicaBzoFOig9F7X0s583wVlXPvaGbmWct3e' .
	'vzqUZGVxK1Y7kUeu27p0Dkt92/7G+X2uuHz8lue5QI1cJR1auxtoltN8NQKq+DQyDvSp0G+Q//dD' .
	'W0fc4gf9UTSuybWllKMrwYTolCVgTYJHEXhC+iDH0pKYaMBJcl5SN5fX0sETpSwBa3Jt3dISfRoZ' .
	'BxpooIEGGmiggQYaaKCBBhpooB3lCc5C4kpZ4AwVAAAAAElFTkSuQmCC'
);
define
(
	'IMG_45x90_MALF_REQ', // 45x90, red on pink: "ERROR: malformed request"
	'iVBORw0KGgoAAAANSUhEUgAAAC0AAABaCAYAAAAsPd/jAAABdUlEQVRo3u2aSw6FIAxF9YWFuP9V' .
	'sRPfiMQoLS0gojkkDvifQPHS6rrHuC8vS7/lhQlooIEGGmiggQYa6C9Ch0vJtl1bxZivS+Wluta+' .
	'RehSp/Nkx7xUl6COeWvfR81DAiylzM4HU0NpKzUT8IBpfTPj1JuHx/Y9O2Ho+2vabmmCXN0xr4Fp' .
	'46rQ6aCkpxX8fAAtu5naZcZfCSEADTTQQAMNNNBAA13piViuuU3e+AgfkbhHybmVnE+Ls5sLKUi+' .
	'YVXcwxKLOOZLsZAzsGWc22260wGzhiJClwm0ENcNh3b8Smtnw5jwxoGeHtp5eFnp52Xcs501Et/w' .
	'Pq9/T1sk3lM+1dW0t8QPge54jx4DnQu6x2iK9iPjQAM9+X3kQzLu8ZYlSS993JfGznGc3vWhu0x7' .
	'/UnJE1f+RAg9bKzZVp1+ox26lxxL4zg+6AfzRB459pRrki7YdF8ZL/wRg7jMfA1lpYEGGmiggQYa' .
	'aKCBBhpooIHW0h8839XO8L3K4AAAAABJRU5ErkJggg=='
);
define
(
	'IMG_1x1_BLACK', // 1x1, single black pixel
	'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAxJREFUCNdj' .
	'YGBgAAAABAABJzQnCgAAAABJRU5ErkJggg=='
);
define
(
	'IMG_76x17_ERROR', // 76x17, red on white: "ERROR"
	'iVBORw0KGgoAAAANSUhEUgAAAEwAAAARCAYAAAB3h0oCAAAAAXNSR0IArs4c6QAAALBJREFUWMPt' .
	'WFsOwCAIG4v3vzL7WEyWxQdVwM1A4l/F2iHVETPzESGOMyTAInURRP0suUhb2FIho/jWXO38w4KN' .
	'LPDGEt2jlgPBZxFKc2o8UT7Lj6SkAmfw1nx+28MkVWQlcjT9EOwjLqnpaNImi+I1j/YSl5RY/gx+' .
	'VCCF/MnkCz4JZQtvEUXx1nyW9jCUlPVLbTJ/3MO2dsnWRq2Nwl2wTarM51rhsVEnDhT/w7C4APaJ' .
	'ZhkIGYaUAAAAAElFTkSuQmCC'
);

// The default approach is to treat an error as fatal, in which case
// some message is output and the user is left there. Inheriting classes
// represent more specific cases, some of which can be handled in a
// "softer" way (see below).
class RackTablesError extends Exception
{
	const INTERNAL = 0; // the default code
	const DB_WRITE_FAILED = 3;
	const NOT_AUTHENTICATED = 4;
	const MISCONFIGURED = 6;
	protected final function genHTMLPage ($title, $text)
	{
		global $helpdesk_banner, $debug_mode;
		if (isset ($debug_mode) && $debug_mode && $this->code != RackTablesError::NOT_AUTHENTICATED)
		{
			// in debug mode, dump backtrace instead of standard page
			// NOT_AUTHENTICATED exception does not need debugging, so it is skipped
			printGenericException ($this);
			return;
		}

		header ('Content-Type: text/html; charset=UTF-8');
		echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n";
		echo '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">'."\n";
		echo "<head><title>${title}</title>";
		echo "</head><body>${text}";
		if (isset ($helpdesk_banner))
			echo '<hr>' . $helpdesk_banner;
		echo '</body></html>';
	}
	protected static function formatString ($string)
	{
		return isCLIMode() ? $string : stringForLabel ($string, 80);
	}
	public function dispatch()
	{
		$msgheader = array
		(
			self::MISCONFIGURED => 'Configuration error',
			self::INTERNAL => 'Internal error',
			self::DB_WRITE_FAILED => 'Database write failed',
		);
		$msgbody = array
		(
			self::MISCONFIGURED => '<h2>Configuration error</h2><br>' . $this->message,
			self::INTERNAL => '<h2>Internal error</h2><br>' . $this->message,
			self::DB_WRITE_FAILED => '<h2>Database write failed</h2><br>' . $this->message,
		);
		switch ($this->code)
		{
		case self::NOT_AUTHENTICATED:
			header ('WWW-Authenticate: Basic realm="' . getConfigVar ('enterprise') . ' RackTables access"');
			header ('HTTP/1.1 401 Unauthorized');
			$this->genHTMLPage ('Not authenticated', '<h2>This system requires authentication. You should use a username and a password.</h2>');
			break;
		case self::MISCONFIGURED:
		case self::INTERNAL:
		case self::DB_WRITE_FAILED:
			$this->genHTMLPage ($msgheader[$this->code], $msgbody[$this->code]);
			break;
		default:
			throw new RackTablesError ('Dispatching error, unknown code ' . $this->code, RackTablesError::INTERNAL);
		}
	}
}

class EntityNotFoundException extends RackTablesError
{
	function __construct ($realm, $id)
	{
		parent::__construct ("Record '${realm}'#'${id}' does not exist");
	}
	public function dispatch()
	{
		global $debug_mode;
		if (isset ($debug_mode) && $debug_mode)
		{
			printGenericException ($this);
			return;
		}
		showError ($this->message);
		redirectUser (buildRedirectURL ('index', 'default'));
	}
}

class ERetryNeeded extends RackTablesError
{
	function __construct ($message)
	{
		$this->code = parent::INTERNAL;
		parent::__construct ($message);
	}
}

class InvalidArgException extends RackTablesError
{
	// derive an instance of InvalidRequestArgException
	public function newIRAE ($argname = NULL)
	{
		if ($argname === NULL)
			return new InvalidRequestArgException ($this->name, $this->value, $this->reason);
		return new InvalidRequestArgException ($argname, $_REQUEST[$argname], $this->reason);
	}
	function __construct ($name, $value, $reason = NULL)
	{
		$message = 'Argument \'' . self::formatString ($name) . '\'' .
			' of value ' . self::formatString (var_export ($value, TRUE), 200) .
			' is invalid' . (is_null ($reason) ? '' : ' (' . self::formatString ($reason, 100) . ')') .
			'.';
		parent::__construct ($message, parent::INTERNAL);
		$this->name = $name;
		$this->value = $value;
		$this->reason = $reason;
	}
	// Instead of the two methods below it would be better to have a single method
	// like setArgumentName() in order not to expose unnecessary details to the
	// users of the class. However, this is not possible because the Exception
	// class does not allow to redefine the message string, which the constructor
	// assigns based on the argument name.
	public function getValue()
	{
		return $this->value;
	}
	public function getReason()
	{
		return $this->reason;
	}
}

// this simplifies construction and helps in catching "soft"
// errors (invalid input from the user)
class InvalidRequestArgException extends InvalidArgException
{
	public function dispatch()
	{
		RackTablesError::genHTMLPage ('Assertion failed', '<h2>Assertion failed</h2><br>' . $this->message);
	}
}

// This wraps certain known PDO errors and is caught in index.php?module=redirect
// and elsewhere to be handled as a "soft" error.
class RTDatabaseError extends RackTablesError
{
	public function dispatch()
	{
		RackTablesError::genHTMLPage ('Database soft error', '<h2>Database soft error</h2><br>' . $this->message);
	}
}

// This specifically means the error condition that happens when the database user
// does not have the privileges to execute the query. The code that catches this
// exception class has to interpret what it actually means based on the query it
// was trying to execute. If not specifically expected, the exception will end up
// in the same catch blocks as RTDatabaseError.
class RTDBTableAccessDenied extends RTDatabaseError
{
	public function dispatch()
	{
		RackTablesError::genHTMLPage ('Database table access denied', '<h2>Database table access denied</h2><br>' . $this->message);
	}
};

// gateway failure is a common case of a "soft" error, some functions do catch this
class RTGatewayError extends RackTablesError
{
	public function dispatch()
	{
		RackTablesError::genHTMLPage ('Gateway error', '<h2>Gateway error</h2><br>' . $this->message);
	}
}

# "Permission denied" is a very common case that in some situations is
# treated as a "soft" error.
class RTPermissionDenied extends RackTablesError
{
	public function dispatch()
	{
		global $pageno, $tabno,
			$user_given_tags,
			$target_given_tags,
			$auto_tags,
			$expl_tags,
			$impl_tags;
		header ('Content-Type: text/html; charset=UTF-8');
		echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' . "\n";
		echo '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">' . "\n";
		echo "<head><title>RackTables: access denied</title>\n";
		printPageHeaders();
		echo '</head><body>';
		echo "<table border=1 cellspacing=0 cellpadding=3 width='50%' align=center>\n";
		echo '<tr><th colspan=2><h3>' . getImageHREF ('DENIED') . ' access denied ';
		echo getImageHREF ('DENIED') . '</h3></th></tr>';
		echo '<tr><th width="50%" class=tagchain>User given tags:</th><td class=tagchain>';
		echo serializeTags ($user_given_tags) . "&nbsp;</td></tr>\n";
		echo '<tr><th width="50%" class=tagchain>Target given tags:</th><td class=tagchain>';
		echo serializeTags ($target_given_tags) . "&nbsp;</td></tr>\n";
		echo '<tr><th width="50%" class=tagchain>Effective explicit tags:</th><td class=tagchain>';
		echo serializeTags ($expl_tags) . "&nbsp;</td></tr>\n";
		echo '<tr><th width="50%" class=tagchain>Effective implicit tags:</th><td class=tagchain>';
		echo serializeTags ($impl_tags) . "&nbsp;</td></tr>\n";
		echo '<tr><th width="50%" class=tagchain>Automatic tags:</th><td class=tagchain>';
		echo serializeTags ($auto_tags) . "&nbsp;</td></tr>\n";
		echo "<tr><th width='50%' class=tdright>Requested page:</th><td class=tdleft>${pageno}</td></tr>\n";
		echo "<tr><th width='50%' class=tdright>Requested tab:</th><td class=tdleft>${tabno}</td></tr>\n";
		echo "<tr><td colspan=2 align=center>Click <a href='index.php?logout'>here</a> to logout.</td></tr>\n";
		echo "</table>\n";
		echo '</body></html>';
	}
}

class RackCodeError extends RackTablesError
{
	protected $lineno;
	function __construct ($message, $lineno = 'unknown')
	{
		# RackCodeError without a catch-block is very likely an internal error
		parent::__construct ($message, self::INTERNAL);
		$this->lineno = $lineno;
	}
	public function dispatch()
	{
		parent::genHTMLPage ('RackCode error', '<h2>RackCode error on line ' . $this->lineno . '</h2><br>' . $this->message);
	}
}

// Whether there is a failure to produce a normal image, this class will emit one
// of the hardcoded last-resort images without the dependency on PHP-GD.
class RTImageError extends RackTablesError
{
	protected $imgbin;
	function __construct ($subject = NULL)
	{
		$map = array
		(
			'pbar_error' => IMG_100x10_PBAR_ERROR,
			'pbar_php_gd_error' => IMG_100x10_PHP_GD_NA,
			'pbar_arg_error' => IMG_100x10_ARG_ERROR,
			'rack_php_gd_error' => IMG_45x90_PHP_GD_NA,
			'rack_not_found' => IMG_45x90_RACK_NOT_FOUND,
			'rack_arg_error' => IMG_45x90_MALF_REQ,
			'access_denied' => IMG_1x1_BLACK,
		);
		$this->imgbin = base64_decode (array_fetch ($map, $subject, IMG_76x17_ERROR));
	}
	public function dispatch()
	{
		header ('Content-Type: image/png');
		echo $this->imgbin;
	}
}

function dumpArrayElement ($x)
{
	switch (gettype ($x))
	{
	case 'integer':
		echo $x;
		break;
	case 'array':
		dumpArray ($x);
		break;
	default:
		echo stringForTD ($x, 100);
	}
}

function dumpArray ($arr)
{
	$self = __FUNCTION__;
	echo '<table><caption>(array)</caption>';
	foreach ($arr as $key => $value)
	{
		echo '<tr valign=top><th class=tdright>';
		// stringForTD() does not handle key 0 properly.
		dumpArrayElement ($key);
		echo '</th><td>';
		dumpArrayElement ($value);
		echo '</td></tr>';
	}
	echo '</table>';
}

function stringTrace ($trace)
{
	$ret = '';
	foreach ($trace as $line)
	{
		if (isset ($line['file']) && isset ($line['line']))
			$ret .= $line['file'] . ':' . $line['line'] . ' ';
		$ret .= $line['function'] . '(';
		$f = TRUE;
		if (isset ($line['args']) && is_array ($line['args']))
			foreach ($line['args'] as $arg)
			{
				if (! $f)
					$ret .= ', ';
				if (is_string ($arg))
					$printarg = '\'' . $arg . '\'';
				elseif (is_null ($arg))
					$printarg = 'NULL';
				elseif (is_array ($arg))
					$printarg = print_r ($arg, 1);
				elseif (is_object ($arg))
					$printarg = 'Object(' . get_class ($arg) . ')';
				else
					$printarg = $arg;
				$ret .= $printarg;
				$f = FALSE;
			}
		$ret .= ")\n";
	}
	return $ret;
}

function printPDOException ($e)
{
	header ('HTTP/1.1 500 Internal Server Error');
	header ('Content-Type: text/html; charset=UTF-8');
	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' . "\n";
	echo '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">' . "\n";
	echo "<head><title>PDOException</title>\n";
	echo "<link rel=stylesheet type='text/css' href='?module=chrome&uri=css/pi.css' />\n";
	echo "<link rel=icon href='?module=chrome&uri=pix/favicon.ico' type='image/x-icon' />\n";
	echo '</head><body>';
	echo '<h2>PDOException</h2><code>' . $e->getMessage() . '</code> (<code>' . $e->getCode() . '</code>)';
	echo '<p>at file <code>' . $e->getFile() . '</code>, line <code>' . $e->getLine() . '</code></p><pre>';
	echo stringTrace ($e->getTrace());
	echo '</pre>';
	echo '<h2>Error info:</h2>';
	echo '<pre>';
	print_r ($e->errorInfo);
	echo '</pre>';
	echo '<h2>Parameters:</h2>';
	echo '<h3>GET</h3>';
	dumpArray ($_GET);
	echo '<h3>POST</h3>';
	dumpArray ($_POST);
	echo '<h3>COOKIE</h3>';
	dumpArray ($_COOKIE);
	echo '</body></html>';
}

function printGenericException ($e)
{
	header('HTTP/1.1 500 Internal Server Error');
	header ('Content-Type: text/html; charset=UTF-8');
	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' . "\n";
	echo '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">' . "\n";
	echo "<head><title> Exception </title>\n";
	echo "<link rel=stylesheet type='text/css' href='?module=chrome&uri=css/pi.css' />\n";
	echo "<link rel=icon href='?module=chrome&uri=pix/favicon.ico' type='image/x-icon' />\n";
	echo '</head> <body>';
	echo '<h2>Uncaught exception: ' . get_class ($e) . '</h2><code>' . $e->getMessage() . '</code> (<code>' . $e->getCode() . '</code>)';
	echo '<p>at file <code>' . $e->getFile() . '</code>, line <code>' . $e->getLine() . '</code></p><pre>';
	echo stringTrace ($e->getTrace());
	echo '</pre>';
	echo '<h2>Parameters:</h2>';
	echo '<h3>GET</h3>';
	dumpArray ($_GET);
	echo '<h3>POST</h3>';
	dumpArray ($_POST);
	echo '<h3>COOKIE</h3>';
	dumpArray ($_COOKIE);
	echo '</body></html>';
}

function printException ($e)
{
	if ($e instanceof RackTablesError)
		$e->dispatch();
	elseif ($e instanceof PDOException)
		printPDOException ($e);
	else
		printGenericException ($e);
}
