<?php
namespace SimpleSAML\Utils;

/**
 * System-related utility methods.
 *
 * @package SimpleSAMLphp
 */
class System
{

    const WINDOWS = 1;
    const LINUX = 2;
    const OSX = 3;
    const HPUX = 4;
    const UNIX = 5;
    const BSD = 6;
    const IRIX = 7;
    const SUNOS = 8;


    /**
     * This function returns the Operating System we are running on.
     *
     * @return mixed A predefined constant identifying the OS we are running on. False if we are unable to determine it.
     *
     * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
     */
    public static function getOS()
    {
        if (stristr(PHP_OS, 'LINUX')) {
            return self::LINUX;
        }
        if (stristr(PHP_OS, 'DARWIN')) {
            return self::OSX;
        }
        if (stristr(PHP_OS, 'WIN')) {
            return self::WINDOWS;
        }
        if (stristr(PHP_OS, 'BSD')) {
            return self::BSD;
        }
        if (stristr(PHP_OS, 'UNIX')) {
            return self::UNIX;
        }
        if (stristr(PHP_OS, 'HP-UX')) {
            return self::HPUX;
        }
        if (stristr(PHP_OS, 'IRIX')) {
            return self::IRIX;
        }
        if (stristr(PHP_OS, 'SUNOS')) {
            return self::SUNOS;
        }
        return false;
    }


    /**
     * This function retrieves the path to a directory where temporary files can be saved.
     *
     * @return string Path to a temporary directory, without a trailing directory separator.
     * @throws \SimpleSAML_Error_Exception If the temporary directory cannot be created or it exists and does not belong
     * to the current user.
     *
     * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
     * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
     * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
     */
    public static function getTempDir()
    {
        $globalConfig = \SimpleSAML_Configuration::getInstance();

        $tempDir = rtrim(
            $globalConfig->getString(
                'tempdir',
                sys_get_temp_dir().DIRECTORY_SEPARATOR.'simplesaml'
            ),
            DIRECTORY_SEPARATOR
        );

        if (!is_dir($tempDir)) {
            if (!mkdir($tempDir, 0700, true)) {
                $error = error_get_last();
                throw new \SimpleSAML_Error_Exception(
                    'Error creating temporary directory "'.$tempDir.'": '.
                    (is_array($error) ? $error['message'] : 'no error available')
                );
            }
        } elseif (function_exists('posix_getuid')) {
            // check that the owner of the temp directory is the current user
            $stat = lstat($tempDir);
            if ($stat['uid'] !== posix_getuid()) {
                throw new \SimpleSAML_Error_Exception(
                    'Temporary directory "'.$tempDir.'" does not belong to the current user.'
                );
            }
        }

        return $tempDir;
    }


    /**
     * Resolve a (possibly) relative path from the given base path.
     *
     * A path which starts with a '/' is assumed to be absolute, all others are assumed to be
     * relative. The default base path is the root of the SimpleSAMLphp installation.
     *
     * @param string      $path The path we should resolve.
     * @param string|null $base The base path, where we should search for $path from. Default value is the root of the
     *     SimpleSAMLphp installation.
     *
     * @return string An absolute path referring to $path.
     *
     * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
     */
    public static function resolvePath($path, $base = null)
    {
        if ($base === null) {
            $config = \SimpleSAML_Configuration::getInstance();
            $base = $config->getBaseDir();
        }

        // normalise directory separator
        $base = str_replace('\\', '/', $base);
        $path = str_replace('\\', '/', $path);

        // remove trailing slashes
        $base = rtrim($base, '/');
        $path = rtrim($path, '/');

        // check for absolute path
        if (substr($path, 0, 1) === '/') {
            // absolute path. */
            $ret = '/';
        } elseif (static::pathContainsDriveLetter($path)) {
            $ret = '';
        } else {
            // path relative to base
            $ret = $base;
        }

        $path = explode('/', $path);
        foreach ($path as $d) {
            if ($d === '.') {
                continue;
            } elseif ($d === '..') {
                $ret = dirname($ret);
            } else {
                if ($ret && substr($ret, -1) !== '/') {
                    $ret .= '/';
                }
                $ret .= $d;
            }
        }

        return $ret;
    }


    /**
     * Atomically write a file.
     *
     * This is a helper function for writing data atomically to a file. It does this by writing the file data to a
     * temporary file, then renaming it to the required file name.
     *
     * @param string $filename The path to the file we want to write to.
     * @param string $data The data we should write to the file.
     * @param int    $mode The permissions to apply to the file. Defaults to 0600.
     *
     * @throws \InvalidArgumentException If any of the input parameters doesn't have the proper types.
     * @throws \SimpleSAML_Error_Exception If the file cannot be saved, permissions cannot be changed or it is not
     *     possible to write to the target file.
     *
     * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
     * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
     * @author Andjelko Horvat
     * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
     *
     * @return void
     */
    public static function writeFile($filename, $data, $mode = 0600)
    {
        if (!is_string($filename) || !is_string($data) || !is_numeric($mode)) {
            throw new \InvalidArgumentException('Invalid input parameters');
        }

        $tmpFile = self::getTempDir().DIRECTORY_SEPARATOR.rand();

        $res = @file_put_contents($tmpFile, $data);
        if ($res === false) {
            $error = error_get_last();
            throw new \SimpleSAML_Error_Exception(
                'Error saving file "'.$tmpFile.'": '.
                (is_array($error) ? $error['message'] : 'no error available')
            );
        }

        if (self::getOS() !== self::WINDOWS) {
            if (!chmod($tmpFile, $mode)) {
                unlink($tmpFile);
                $error = error_get_last();
                //$error = (is_array($error) ? $error['message'] : 'no error available');
                throw new \SimpleSAML_Error_Exception(
                    'Error changing file mode of "'.$tmpFile.'": '.
                    (is_array($error) ? $error['message'] : 'no error available')
                );
            }
        }

        if (!rename($tmpFile, $filename)) {
            unlink($tmpFile);
            $error = error_get_last();
            throw new \SimpleSAML_Error_Exception(
                'Error moving "'.$tmpFile.'" to "'.$filename.'": '.
                (is_array($error) ? $error['message'] : 'no error available')
            );
        }

        if (function_exists('opcache_invalidate')) {
            opcache_invalidate($filename);
        }
    }

    /**
     * Check if the supplied path contains a Windows-style drive letter.
     *
     * @param string $path
     *
     * @return bool
     */
    private static function pathContainsDriveLetter($path)
    {
        $letterAsciiValue = ord(strtoupper(substr($path, 0, 1)));
        return substr($path, 1, 1) === ':'
                && $letterAsciiValue >= 65 && $letterAsciiValue <= 90;
    }
}
