appserver-io/webserver

View on GitHub
src/AppserverIo/WebServer/Modules/AutoIndexModule.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php

/**
 * \AppserverIo\WebServer\Modules\AutoIndexModule
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <tw@appserver.io>
 * @author    Johann Zelger <jz@appserver.io>
 * @copyright 2015 TechDivision GmbH <info@appserver.io>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/appserver-io/webserver
 * @link      http://www.appserver.io/
 */

namespace AppserverIo\WebServer\Modules;

use AppserverIo\Http\HttpResponseStates;
use AppserverIo\Psr\HttpMessage\Protocol;
use AppserverIo\Psr\HttpMessage\RequestInterface;
use AppserverIo\Psr\HttpMessage\ResponseInterface;
use AppserverIo\Server\Dictionaries\ServerVars;
use AppserverIo\Server\Dictionaries\ModuleHooks;
use AppserverIo\Server\Exceptions\ModuleException;
use AppserverIo\Server\Interfaces\RequestContextInterface;
use AppserverIo\Server\Interfaces\ServerContextInterface;
use AppserverIo\WebServer\Interfaces\HttpModuleInterface;

/**
 * Module that creates a directory index, if no other module handles
 * the request. If you want to use this module, the modules have to be
 * configured in the following order:
 *
 * <module type="\AppserverIo\WebServer\Modules\VirtualHostModule"/>
 * <module type="\AppserverIo\WebServer\Modules\AuthenticationModule"/>
 * <module type="\AppserverIo\WebServer\Modules\EnvironmentVariableModule" />
 * <module type="\AppserverIo\WebServer\Modules\RewriteModule"/>
 * <module type="\AppserverIo\WebServer\Modules\DirectoryModule"/>
 * <module type="\AppserverIo\WebServer\Modules\AccessModule"/>
 * <module type="\AppserverIo\WebServer\Modules\LocationModule"/>
 * <module type="\AppserverIo\WebServer\Modules\AutoIndexModule"/>
 * <module type="\AppserverIo\WebServer\Modules\CoreModule"/>
 * <module type="\AppserverIo\WebServer\Modules\PhpModule"/>
 * <module type="\AppserverIo\WebServer\Modules\FastCgiModule"/>
 * <module type="\AppserverIo\Appserver\ServletEngine\ServletEngine" />
 *
 * @author    Tim Wagner <tw@appserver.io>
 * @author    Johann Zelger <jz@appserver.io>
 * @copyright 2015 TechDivision GmbH <info@appserver.io>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/appserver-io/webserver
 * @link      http://www.appserver.io/
 */
class AutoIndexModule implements HttpModuleInterface
{

    /**
     * Defines the module name
     *
     * @var string
     */
    const MODULE_NAME = 'autoIndex';

    /**
     * Initiates the module.
     *
     * @param \AppserverIo\Server\Interfaces\ServerContextInterface $serverContext The server's context instance
     *
     * @return bool
     * @throws \AppserverIo\Server\Exceptions\ModuleException
     */
    public function init(ServerContextInterface $serverContext)
    {
    }

    /**
     * Implements module logic for given hook
     *
     * @param \AppserverIo\Psr\HttpMessage\RequestInterface          $request        A request object
     * @param \AppserverIo\Psr\HttpMessage\ResponseInterface         $response       A response object
     * @param \AppserverIo\Server\Interfaces\RequestContextInterface $requestContext A requests context instance
     * @param int                                                    $hook           The current hook to process logic for
     *
     * @return bool
     * @throws \AppserverIo\Server\Exceptions\ModuleException
     */
    public function process(RequestInterface $request, ResponseInterface $response, RequestContextInterface $requestContext, $hook)
    {

        /**
         * @var $request \AppserverIo\Psr\HttpMessage\RequestInterface
         */

        /**
         * @var $response \AppserverIo\Psr\HttpMessage\ResponseInterface
         */

        // if false hook is comming do nothing
        if (ModuleHooks::REQUEST_POST !== $hook) {
            return;
        }

        // make request context available for usage in template
        $this->requestContext = $requestContext;

        // query whether the auto index module is available
        if ($this->getRequestContext()->hasServerVar(ServerVars::SERVER_AUTO_INDEX) === false) {
            return;
        }

        // query whether the auto index module is available and enabled
        if ($this->getRequestContext()->getServerVar(ServerVars::SERVER_AUTO_INDEX) === ServerVars::VALUE_AUTO_INDEX_OFF) {
            return;
        }

        // stop processing if file handler will not be core in case that location module
        // has changed the server handler to be proxy, fastcgi or what ever.
        if ($requestContext->getServerVar(ServerVars::SERVER_HANDLER) !== 'core') {
            return;
        }

        // now load the URL without path information and query string
        $url = $this->getUrl();

        // query whether the URL ends with a slash
        if ($url[strlen($url) - 1] !== '/') {
            return;
        }

        // query whether an existing path is requested
        if (is_dir($realPath = $this->getRealPath()) === false) {
            return;
        }

        // load the auto index template if available
        $autoIndexTemplatePath = $this->getRequestContext()->getServerVar(ServerVars::SERVER_AUTO_INDEX_TEMPLATE_PATH);

        // query whether a template is configured and available
        if ($autoIndexTemplatePath && is_file($autoIndexTemplatePath)) {
            // render errors page
            ob_start();
            require $autoIndexTemplatePath;
            $autoIndexPage = ob_get_clean();

        } else {
            // initialize the directory listing content
            $directoryListing = '<tr><th>Name</th><th>Last Modified</th><th>Size</th></tr>';

            // query whether if we've parent directory or not
            if ($this->hasParent($realPath)) {
                $directoryListing .= sprintf(
                    '<tr><td colspan="3"><a href="%s">Parent Directory</a></td></tr>',
                    $this->getParentLink()
                );
            }

            // append the found files + directories to the directory listing
            foreach ($this->getDirectoryContent($realPath) as $directory) {
                // append the file or directory to the directory listing
                $directoryListing .= sprintf(
                    '<tr><td><a href="%s">%s</a></td><td>%s</td><td>%d</td></tr>',
                    $this->getLink($directory),
                    $this->getName($directory),
                    $this->getDate($directory),
                    $this->getFilesize($directory)
                );
            }

            // concatenate the elements of the auto index page
            $autoIndexPage = sprintf(
                '<!DOCTYPE html><html><head><title>Index of %s</title></head><body><h1>Index of %s</h1><table>%s</table></body></html>',
                $this->getUri(),
                $this->getUri(),
                $directoryListing
            );
        }

        // append errors page to response body
        $response->appendBodyStream($autoIndexPage);

        // set the Content-Type to text/html
        $response->addHeader(Protocol::HEADER_CONTENT_TYPE, 'text/html');

        // set response state to be dispatched after this without calling other modules process
        $response->setState(HttpResponseStates::DISPATCH);
    }

    /**
     * Returns the actual request context instance.
     *
     * @return \AppserverIo\Server\Interfaces\RequestContextInterface The actual requests context instance
     */
    public function getRequestContext()
    {
        return $this->requestContext;
    }

    /**
     * Returns the absolute path for the acutal request URL.
     *
     * @return string The absolute path for the acutal request URL
     */
    public function getRealPath()
    {
        return realpath($this->getDocumentRoot() . $this->getUrl());
    }

    /**
     * Returns an array with the content of the passed directory.
     *
     * @param string $realPath The absolute path for the acutal request URL
     *
     * @return array The content of the actual directory
     */
    public function getDirectoryContent($realPath)
    {
        return glob(sprintf('%s/*', $realPath));
    }

    /**
     * Returns the actual, by previous modules pre-processed, request URI.
     *
     * @return string The actual pre-processed request URI
     */
    public function getUri()
    {
        return $this->getRequestContext()->getServerVar(ServerVars::X_REQUEST_URI);
    }

    /**
     * Returns the document root for the actual request.
     *
     * @return string The document root for the actual request
     */
    public function getDocumentRoot()
    {
        return $this->getRequestContext()->getServerVar(ServerVars::DOCUMENT_ROOT);
    }

    /**
     * Returns the acutal request URL.
     *
     * @return string The actual request URL
     */
    public function getUrl()
    {
        return parse_url($this->getUri(), PHP_URL_PATH);
    }

    /**
     * Queries whether the passed path has a parent directory we want to render.
     *
     * @param string $realPath The absolute path to query for a parent directory
     *
     * @return boolean TRUE if we've a parent directory, else FALSE
     */
    public function hasParent($realPath)
    {
        return $this->getDocumentRoot() !== $realPath;
    }

    /**
     * Returns the relative path to the parent directory for the actual request URL.
     *
     * @return string the relative path to the parent directory
     */
    public function getParentLink()
    {
        return dirname($this->getUrl());
    }

    /**
     * Returns the relative path to the documention root for the passed directory.
     *
     * @param string $directory The directory to return the relative path to the document root for
     *
     * @return mixed The relative path to the document root for the passed directory
     */
    public function getIcon($directory)
    {
        if (is_file($directory)) {
            return pathinfo($directory, PATHINFO_EXTENSION);
        }
        // if it not a file it should be an directory
        return 'dir';
    }

    /**
     * Returns the relative path to the documention root for the passed directory.
     *
     * @param string $directory The directory to return the relative path to the document root for
     *
     * @return mixed The relative path to the document root for the passed directory
     */
    public function getLink($directory)
    {
        return str_replace($this->getDocumentRoot(), '', $directory);
    }

    /**
     * Returns the name of last element of the passed directory.
     *
     * @param string $directory The directory to return the last element from
     *
     * @return string The name of the last directory element
     */
    public function getName($directory)
    {
        return basename($directory);
    }

    /**
     * Returns the modification time of the last element of the passed directory.
     *
     * @param string $directory The directory to return the last elements modification date from
     *
     * @return string The modification date of the last directory element
     */
    public function getDate($directory)
    {
        return date('Y-m-d H:i:s', filemtime($directory));
    }

    /**
     * Returns the filesize of the last element of the passed directory.
     *
     * @param string $directory The directory to return the last elements filesize from
     *
     * @return string The filesize of the last directory element
     */
    public function getFilesize($directory)
    {
        $bytes = filesize($directory);
        if ($bytes >= 1073741824) {
            $bytes = number_format($bytes / 1073741824, 2) . ' GB';
        } elseif ($bytes >= 1048576) {
            $bytes = number_format($bytes / 1048576, 2) . ' MB';
        } elseif ($bytes >= 1024) {
            $bytes = number_format($bytes / 1024, 2) . ' KB';
        } elseif ($bytes > 1) {
            $bytes = $bytes . ' bytes';
        } elseif ($bytes == 1) {
            $bytes = $bytes . ' byte';
        } else {
            $bytes = '0 bytes';
        }
        // return formated bytes
        return $bytes;
    }

    /**
     * Return's an array of module names which should be executed first
     *
     * @return array The array of module names
     */
    public function getDependencies()
    {
        return array();
    }

    /**
     * Returns the module name
     *
     * @return string The module name
     */
    public function getModuleName()
    {
        return self::MODULE_NAME;
    }

    /**
     * Prepares the module for upcoming request in specific context
     *
     * @return bool
     * @throws \AppserverIo\Server\Exceptions\ModuleException
     */
    public function prepare()
    {
        // nothing to prepare for this module
    }
}