| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * files.php |
| 5 | * |
| 6 | * This file includes various helper functions for working |
| 7 | * with the server filesystem. |
| 8 | * |
| 9 | * @copyright © 2008-2008 The SquirrelMail Project Team |
| 10 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License |
| 11 | * @version $Id$ |
| 12 | * @package squirrelmail |
| 13 | */ |
| 14 | |
| 15 | |
| 16 | /** |
| 17 | * Generates a unique file in a specific directory and |
| 18 | * returns the file name (without the path). |
| 19 | * |
| 20 | * @param directory The directory within which to create the file |
| 21 | * |
| 22 | * @return mixed FALSE when a failure occurs, otherwise a string |
| 23 | * containing the filename of the file only (not |
| 24 | * its full path) |
| 25 | * |
| 26 | * @since 1.5.2 |
| 27 | * |
| 28 | */ |
| 29 | function sq_create_tempfile($directory) |
| 30 | { |
| 31 | |
| 32 | // give up after 1000 tries |
| 33 | $maximum_tries = 1000; |
| 34 | |
| 35 | // using PHP >= 4.3.2 we can be truly atomic here |
| 36 | $filemods = check_php_version(4, 3, 2) ? 'x' : 'w'; |
| 37 | |
| 38 | for ($try = 0; $try < $maximum_tries; ++$try) { |
| 39 | |
| 40 | $localfilename = GenerateRandomString(32, '', 7); |
| 41 | $full_localfilename = $directory . DIRECTORY_SEPARATOR . $localfilename; |
| 42 | |
| 43 | // filename collision. try again |
| 44 | if ( file_exists($full_localfilename) ) { |
| 45 | continue; |
| 46 | } |
| 47 | |
| 48 | // try to open for (binary) writing |
| 49 | $fp = @fopen( $full_localfilename, $filemods); |
| 50 | |
| 51 | if ($fp !== FALSE) { |
| 52 | // success! make sure it's not readable, close and return filename |
| 53 | chmod($full_localfilename, 0600); |
| 54 | fclose($fp); |
| 55 | return $localfilename; |
| 56 | } |
| 57 | |
| 58 | } |
| 59 | |
| 60 | // we tried as many times as we could but didn't succeed. |
| 61 | return FALSE; |
| 62 | |
| 63 | } |
| 64 | |
| 65 | |
| 66 | /** |
| 67 | * PHP's is_writable() is broken in some versions due to either |
| 68 | * safe_mode or because of problems correctly determining the |
| 69 | * actual file permissions under Windows. Under safe_mode or |
| 70 | * Windows, we'll try to actually write something in order to |
| 71 | * see for sure... |
| 72 | * |
| 73 | * @param string $path The full path to the file or directory to |
| 74 | * be tested |
| 75 | * |
| 76 | * @return boolean Whether or not the file or directory exists |
| 77 | * and is writable |
| 78 | * |
| 79 | * @since 1.5.2 |
| 80 | * |
| 81 | **/ |
| 82 | function sq_is_writable($path) { |
| 83 | |
| 84 | global $server_os; |
| 85 | |
| 86 | |
| 87 | // under *nix with safe_mode off, use the native is_writable() |
| 88 | // |
| 89 | if ($server_os == '*nix' && !(bool)ini_get('safe_mode')) |
| 90 | return is_writable($path); |
| 91 | |
| 92 | |
| 93 | // if it's a directory, that means we have to create a temporary |
| 94 | // file therein |
| 95 | // |
| 96 | $delete_temp_file = FALSE; |
| 97 | if (@is_dir($path) && ($temp_filename = @sq_create_tempfile($path))) |
| 98 | { |
| 99 | $path .= DIRECTORY_SEPARATOR . $temp_filename; |
| 100 | $delete_temp_file = TRUE; |
| 101 | } |
| 102 | |
| 103 | |
| 104 | // try to open the file for writing (without trying to create it) |
| 105 | // |
| 106 | if (!@is_dir($path) && ($FILE = @fopen($path, 'r+'))) |
| 107 | { |
| 108 | @fclose($FILE); |
| 109 | |
| 110 | // delete temp file if needed |
| 111 | // |
| 112 | if ($delete_temp_file) |
| 113 | @unlink($path); |
| 114 | |
| 115 | return TRUE; |
| 116 | } |
| 117 | |
| 118 | |
| 119 | // delete temp file if needed |
| 120 | // |
| 121 | if ($delete_temp_file) |
| 122 | @unlink($path); |
| 123 | |
| 124 | return FALSE; |
| 125 | |
| 126 | } |
| 127 | |
| 128 | |
| 129 | /** |
| 130 | * Find files and/or directories in a given directory optionally |
| 131 | * limited to only those with the given file extension. If the |
| 132 | * directory is not found or cannot be opened, no error is generated; |
| 133 | * only an empty file list is returned. |
| 134 | FIXME: do we WANT to throw an error or a notice or... or return FALSE? |
| 135 | * |
| 136 | * @param string $directory_path The path (relative or absolute) |
| 137 | * to the desired directory. |
| 138 | * @param mixed $extension The file extension filter - either |
| 139 | * an array of desired extension(s), |
| 140 | * or a comma-separated list of same |
| 141 | * (optional; default is to return |
| 142 | * all files (dirs). |
| 143 | * @param boolean $return_filenames_only When TRUE, only file/dir names |
| 144 | * are returned, otherwise the |
| 145 | * $directory_path string is |
| 146 | * prepended to each file/dir in |
| 147 | * the returned list (optional; |
| 148 | * default is filename/dirname only) |
| 149 | * @param boolean $include_directories When TRUE, directories are |
| 150 | * included (optional; default |
| 151 | * DO include directories). |
| 152 | * @param boolean $directories_only When TRUE, ONLY directories |
| 153 | * are included (optional; default |
| 154 | * is to include files too). |
| 155 | * @param boolean $separate_files_and_directories When TRUE, files and |
| 156 | * directories are returned |
| 157 | * in separate lists, so |
| 158 | * the return value is |
| 159 | * formatted as a two-element |
| 160 | * array with the two keys |
| 161 | * "FILES" and "DIRECTORIES", |
| 162 | * where corresponding values |
| 163 | * are lists of either all |
| 164 | * files or all directories |
| 165 | * (optional; default do not |
| 166 | * split up return array). |
| 167 | * @param boolean $only_sm When TRUE, a security check will |
| 168 | * limit directory access to only |
| 169 | * paths within the SquirrelMail |
| 170 | * installation currently being used |
| 171 | * (optional; default TRUE) |
| 172 | * |
| 173 | * @return array The requested file/directory list(s). |
| 174 | * |
| 175 | * @since 1.5.2 |
| 176 | * |
| 177 | */ |
| 178 | function list_files($directory_path, $extensions='', $return_filenames_only=TRUE, |
| 179 | $include_directories=TRUE, $directories_only=FALSE, |
| 180 | $separate_files_and_directories=FALSE, $only_sm=TRUE) { |
| 181 | |
| 182 | $files = array(); |
| 183 | $directories = array(); |
| 184 | |
| 185 | |
| 186 | // make sure requested path is under SM_PATH if needed |
| 187 | // |
| 188 | if ($only_sm) { |
| 189 | if (strpos(realpath($directory_path), realpath(SM_PATH)) !== 0) { |
| 190 | //plain_error_message(_("Illegal filesystem access was requested")); |
| 191 | echo _("Illegal filesystem access was requested"); |
| 192 | exit; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | |
| 197 | // validate given directory |
| 198 | // |
| 199 | if (empty($directory_path) |
| 200 | || !is_dir($directory_path) |
| 201 | || !($DIR = opendir($directory_path))) { |
| 202 | return $files; |
| 203 | } |
| 204 | |
| 205 | |
| 206 | // ensure extensions is an array and is properly formatted |
| 207 | // |
| 208 | if (!empty($extensions)) { |
| 209 | if (!is_array($extensions)) |
| 210 | $extensions = explode(',', $extensions); |
| 211 | $temp_extensions = array(); |
| 212 | foreach ($extensions as $ext) |
| 213 | $temp_extensions[] = '.' . trim(trim($ext), '.'); |
| 214 | $extensions = $temp_extensions; |
| 215 | } else $extensions = array(); |
| 216 | |
| 217 | |
| 218 | $directory_path = rtrim($directory_path, '/'); |
| 219 | |
| 220 | |
| 221 | // parse through the files |
| 222 | // |
| 223 | while (($file = readdir($DIR)) !== false) { |
| 224 | |
| 225 | if ($file == '.' || $file == '..') continue; |
| 226 | |
| 227 | if (!empty($extensions)) |
| 228 | foreach ($extensions as $ext) |
| 229 | if (strrpos($file, $ext) !== (strlen($file) - strlen($ext))) |
| 230 | continue 2; |
| 231 | |
| 232 | // only use is_dir() if we really need to (be as efficient as possible) |
| 233 | // |
| 234 | $is_dir = FALSE; |
| 235 | if (!$include_directories || $directories_only |
| 236 | || $separate_files_and_directories) { |
| 237 | if (is_dir($directory_path . '/' . $file)) { |
| 238 | if (!$include_directories) continue; |
| 239 | $is_dir = TRUE; |
| 240 | $directories[] = ($return_filenames_only |
| 241 | ? $file |
| 242 | : $directory_path . '/' . $file); |
| 243 | } |
| 244 | if ($directories_only) continue; |
| 245 | } |
| 246 | |
| 247 | if (!$separate_files_and_directories |
| 248 | || ($separate_files_and_directories && !$is_dir)) { |
| 249 | $files[] = ($return_filenames_only |
| 250 | ? $file |
| 251 | : $directory_path . '/' . $file); |
| 252 | } |
| 253 | |
| 254 | } |
| 255 | closedir($DIR); |
| 256 | |
| 257 | |
| 258 | if ($directories_only) return $directories; |
| 259 | if ($separate_files_and_directories) return array('FILES' => $files, |
| 260 | 'DIRECTORIES' => $directories); |
| 261 | return $files; |
| 262 | |
| 263 | } |
| 264 | |
| 265 | |