1<?php 2 3namespace SimpleSAML\Utils; 4 5use SimpleSAML\Configuration; 6use SimpleSAML\Error; 7 8/** 9 * System-related utility methods. 10 * 11 * @package SimpleSAMLphp 12 */ 13 14class System 15{ 16 const WINDOWS = 1; 17 const LINUX = 2; 18 const OSX = 3; 19 const HPUX = 4; 20 const UNIX = 5; 21 const BSD = 6; 22 const IRIX = 7; 23 const SUNOS = 8; 24 25 26 /** 27 * This function returns the Operating System we are running on. 28 * 29 * @return mixed A predefined constant identifying the OS we are running on. False if we are unable to determine it. 30 * 31 * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> 32 */ 33 public static function getOS() 34 { 35 if (stristr(PHP_OS, 'LINUX')) { 36 return self::LINUX; 37 } 38 if (stristr(PHP_OS, 'DARWIN')) { 39 return self::OSX; 40 } 41 if (stristr(PHP_OS, 'WIN')) { 42 return self::WINDOWS; 43 } 44 if (stristr(PHP_OS, 'BSD')) { 45 return self::BSD; 46 } 47 if (stristr(PHP_OS, 'UNIX')) { 48 return self::UNIX; 49 } 50 if (stristr(PHP_OS, 'HP-UX')) { 51 return self::HPUX; 52 } 53 if (stristr(PHP_OS, 'IRIX')) { 54 return self::IRIX; 55 } 56 if (stristr(PHP_OS, 'SUNOS')) { 57 return self::SUNOS; 58 } 59 return false; 60 } 61 62 63 /** 64 * This function retrieves the path to a directory where temporary files can be saved. 65 * 66 * @return string Path to a temporary directory, without a trailing directory separator. 67 * @throws Error\Exception If the temporary directory cannot be created or it exists and does not belong 68 * to the current user. 69 * 70 * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> 71 * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> 72 * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> 73 */ 74 public static function getTempDir() 75 { 76 $globalConfig = Configuration::getInstance(); 77 78 $tempDir = rtrim( 79 $globalConfig->getString( 80 'tempdir', 81 sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'simplesaml' 82 ), 83 DIRECTORY_SEPARATOR 84 ); 85 86 if (!is_dir($tempDir)) { 87 if (!mkdir($tempDir, 0700, true)) { 88 $error = error_get_last(); 89 throw new Error\Exception( 90 'Error creating temporary directory "' . $tempDir . '": ' . 91 (is_array($error) ? $error['message'] : 'no error available') 92 ); 93 } 94 } elseif (function_exists('posix_getuid')) { 95 // check that the owner of the temp directory is the current user 96 $stat = lstat($tempDir); 97 if ($stat['uid'] !== posix_getuid()) { 98 throw new Error\Exception( 99 'Temporary directory "' . $tempDir . '" does not belong to the current user.' 100 ); 101 } 102 } 103 104 return $tempDir; 105 } 106 107 108 /** 109 * Resolve a (possibly) relative path from the given base path. 110 * 111 * A path which starts with a stream wrapper pattern (e.g. s3://) will not be touched 112 * and returned as is - regardles of the value given as base path. 113 * If it starts with a '/' it is assumed to be absolute, all others are assumed to be 114 * relative. The default base path is the root of the SimpleSAMLphp installation. 115 * 116 * @param string $path The path we should resolve. 117 * @param string|null $base The base path, where we should search for $path from. Default value is the root of the 118 * SimpleSAMLphp installation. 119 * 120 * @return string An absolute path referring to $path. 121 * 122 * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> 123 */ 124 public static function resolvePath($path, $base = null) 125 { 126 if ($base === null) { 127 $config = Configuration::getInstance(); 128 $base = $config->getBaseDir(); 129 } 130 131 // normalise directory separator 132 $base = str_replace('\\', '/', $base); 133 $path = str_replace('\\', '/', $path); 134 135 // remove trailing slashes 136 $base = rtrim($base, '/'); 137 $path = rtrim($path, '/'); 138 139 // check for absolute path 140 if (substr($path, 0, 1) === '/') { 141 // absolute path. */ 142 $ret = '/'; 143 } elseif (static::pathContainsDriveLetter($path)) { 144 $ret = ''; 145 } else { 146 // path relative to base 147 $ret = $base; 148 } 149 150 if (static::pathContainsStreamWrapper($path)) { 151 $ret = $path; 152 } else { 153 $path = explode('/', $path); 154 foreach ($path as $d) { 155 if ($d === '.') { 156 continue; 157 } elseif ($d === '..') { 158 $ret = dirname($ret); 159 } else { 160 if ($ret && substr($ret, -1) !== '/') { 161 $ret .= '/'; 162 } 163 $ret .= $d; 164 } 165 } 166 } 167 168 return $ret; 169 } 170 171 172 /** 173 * Atomically write a file. 174 * 175 * This is a helper function for writing data atomically to a file. It does this by writing the file data to a 176 * temporary file, then renaming it to the required file name. 177 * 178 * @param string $filename The path to the file we want to write to. 179 * @param string $data The data we should write to the file. 180 * @param int $mode The permissions to apply to the file. Defaults to 0600. 181 * 182 * @throws \InvalidArgumentException If any of the input parameters doesn't have the proper types. 183 * @throws Error\Exception If the file cannot be saved, permissions cannot be changed or it is not 184 * possible to write to the target file. 185 * 186 * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> 187 * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> 188 * @author Andjelko Horvat 189 * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> 190 * 191 * @return void 192 */ 193 public static function writeFile($filename, $data, $mode = 0600) 194 { 195 if (!is_string($filename) || !is_string($data) || !is_numeric($mode)) { 196 throw new \InvalidArgumentException('Invalid input parameters'); 197 } 198 199 $tmpFile = self::getTempDir() . DIRECTORY_SEPARATOR . rand(); 200 201 $res = @file_put_contents($tmpFile, $data); 202 if ($res === false) { 203 $error = error_get_last(); 204 throw new Error\Exception( 205 'Error saving file "' . $tmpFile . '": ' . 206 (is_array($error) ? $error['message'] : 'no error available') 207 ); 208 } 209 210 if (self::getOS() !== self::WINDOWS) { 211 if (!chmod($tmpFile, $mode)) { 212 unlink($tmpFile); 213 $error = error_get_last(); 214 //$error = (is_array($error) ? $error['message'] : 'no error available'); 215 throw new Error\Exception( 216 'Error changing file mode of "' . $tmpFile . '": ' . 217 (is_array($error) ? $error['message'] : 'no error available') 218 ); 219 } 220 } 221 222 if (!rename($tmpFile, $filename)) { 223 unlink($tmpFile); 224 $error = error_get_last(); 225 throw new Error\Exception( 226 'Error moving "' . $tmpFile . '" to "' . $filename . '": ' . 227 (is_array($error) ? $error['message'] : 'no error available') 228 ); 229 } 230 231 if (function_exists('opcache_invalidate')) { 232 opcache_invalidate($filename); 233 } 234 } 235 236 /** 237 * Check if the supplied path contains a Windows-style drive letter. 238 * 239 * @param string $path 240 * 241 * @return bool 242 */ 243 private static function pathContainsDriveLetter($path) 244 { 245 $letterAsciiValue = ord(strtoupper(substr($path, 0, 1))); 246 return substr($path, 1, 1) === ':' 247 && $letterAsciiValue >= 65 && $letterAsciiValue <= 90; 248 } 249 250 /** 251 * Check if the supplied path contains a stream wrapper 252 * @param string $path 253 * @return bool 254 */ 255 private static function pathContainsStreamWrapper($path) 256 { 257 return preg_match('/^[\w\d]*:\/{2}/', $path) === 1; 258 } 259} 260