<?php
/**
 * @brief		External redirector with key checks
 * @author		<a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright	(c) Invision Power Services, Inc.
 * @license		https://www.invisioncommunity.com/legal/standards/
 * @package		Invision Community
 * @since		12 Jun 2013
 */

namespace IPS\core\modules\front\system;

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
	header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
	exit;
}

/**
 * Redirect
 */
class _redirect extends \IPS\Dispatcher\Controller
{
	/**
	 * Handle munged links
	 *
	 * @return	void
	 */
	protected function manage()
	{
		/* First check the key to make sure this actually came from HTMLPurifier */
		if ( \IPS\Login::compareHashes( hash_hmac( "sha256", \IPS\Request::i()->url, \IPS\Settings::i()->site_secret_key ), (string) \IPS\Request::i()->key ) )
		{
			/* Construct the URL */
			$url = \IPS\Http\Url::external( \IPS\Request::i()->url );
			
			/* If it's a resource (image, etc.), we pull the actual contents to prevent the referrer being exposed (which is an issue in the ACP where the session ID is private) */
			if ( \IPS\Request::i()->resource )
			{
				/* Except if it's internal or localhost, we can't make a HTTP request to it because doing that would potentially allow access to secured resources because the server
					thinks the request is internal. We don't need to worry about about things on this domain getting access to the referrer anyway */
				if ( $url->isLocalhost() )
				{
					\IPS\Output::i()->redirect( $url, NULL, 303 );
				}
				/* For everything else, pull the contents... */
				else
				{
					/* It can't be protocol relative */
					if ( !$url->data['scheme'] )
					{
						$url = \IPS\Http\Url::external( 'http:' . \IPS\Request::i()->url );
					}

					/* Get the contents */
					try
					{
						$response = \IPS\Http\Url::external( $url )->request( \IPS\DEFAULT_REQUEST_TIMEOUT )->get();
					}
					catch ( \Exception $e )
					{
						\IPS\Output::i()->redirect( \IPS\Http\Url::internal('') );
					}
					
					/* Send output - we only want to pass along the content and content type. We can't allow the response
						to perform a redirect (which would cause the referrer to be exposed) and we don't want to pass
						along any of their headers which could include setting cookies, etc. */
					\IPS\Output::i()->sendOutput( $response->content, $response->httpResponseCode == 200 ? 200 : 404, isset( $response->httpHeaders['Content-Type'] ) ? $response->httpHeaders['Content-Type'] : 'unknown/unknown' );
				}
			}
			/* For everything else, we'll do a 303 redirect */
			else
			{
				\IPS\Output::i()->redirect( $url, \IPS\Member::loggedIn()->language()->addToStack('external_redirect'), 303, TRUE );
			}
		}
		/* If it doesn't validate, send the user to the index page */
		else
		{
			\IPS\Output::i()->redirect( \IPS\Http\Url::internal('') );
		}
	}

	/**
	 * Redirect an ACP click
	 *
	 * @note	The purpose of this method is to avoid exposing \IPS\CP_DIRECTORY to non-admins
	 * @return	void
	 */
	protected function admin()
	{
		if( \IPS\Member::loggedIn()->isAdmin() )
		{
			$queryString	= base64_decode( \IPS\Request::i()->_data );
			\IPS\Output::i()->redirect( new \IPS\Http\Url( \IPS\Http\Url::baseUrl() . \IPS\CP_DIRECTORY . '/?' . $queryString, TRUE ) );
		}

		\IPS\Output::i()->error( 'no_access_cp', '2C159/3', 403 );
	}

	/**
	 * Redirect an advertisement click
	 *
	 * @return	void
	 */
	protected function advertisement()
	{
		/* CSRF check */
		\IPS\Session::i()->csrfCheck();

		/* Get the advertisement */
		$advertisement	= array();

		if( isset( \IPS\Request::i()->ad ) )
		{
			try
			{
				$advertisement	= \IPS\core\Advertisement::load( \IPS\Request::i()->ad );
			}
			catch( \OutOfRangeException $e )
			{
				\IPS\Output::i()->error( 'ad_not_found', '2C159/2', 404, 'ad_not_found_admin' );
			}
		}

		if( !$advertisement->id OR !$advertisement->link )
		{
			\IPS\Output::i()->error( 'ad_not_found', '2C159/1', 404, 'ad_not_found_admin' );
		}

		/* We need to update click count for this advertisement. Does it need to be shut off too due to hitting click maximum?
			Note that this needs to be done as a string to do "col=col+1", which is why we're not using the ActiveRecord save() method.
			Updating by doing col=col+1 is more reliable when there are several clicks at nearly the same time. */
		$update	= "ad_clicks=ad_clicks+1";

		if( $advertisement->maximum_unit == 'c' AND $advertisement->maximum_value > -1 AND $advertisement->clicks + 1 >= $advertisement->maximum_value )
		{
			$update	.= ", ad_active=0";
		}

		/* Update the database */
		\IPS\Db::i()->update( 'core_advertisements', $update, array( 'ad_id=?', $advertisement->id ) );

		/* And do the redirect */
		\IPS\Output::i()->redirect( \IPS\Http\Url::external( $advertisement->link ) );
	}
}