\nDeny from all\n\n"); } return $dir; } /** Write (or with $append, add to) a file; a failed or short write throws, so nothing goes on with half a file. */ public static function put(string $path, string $data, bool $append = false): void { if (!self::writable($path) || file_put_contents($path, $data, $append ? FILE_APPEND : 0) !== strlen($data)) { throw new RelayError( 'files.write_failed', /* translators: %s: file path */ esc_html(sprintf(__('Could not write %s.', 'siterelay'), $path)), esc_html__('The disk may be full, or the web server may not be allowed to write there.', 'siterelay') ); } } /** * A file handle, or false when the file can't be opened that way (checked first: fopen() would warn). * * @return resource|false */ public static function open(string $path, string $mode) { if (strpos($path, '://') === false && !self::can_open($path, $mode)) { return false; } return fopen($path, $mode); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streams files of any size in chunks (see the class note) } private static function can_open(string $path, string $mode): bool { if ($mode[0] === 'r') { return is_file($path) && is_readable($path) && (strpos($mode, '+') === false || wp_is_writable($path)); } if (file_exists($path)) { return is_file($path) && wp_is_writable($path) && ($mode[0] !== 'x'); } return is_dir(dirname($path)) && wp_is_writable(dirname($path)); } /** * @param resource $handle * @return string|false */ public static function read($handle, int $length) { return fread($handle, $length); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- streams files of any size in chunks (see the class note) } /** * @param resource $handle * @return int|false */ public static function write($handle, string $data) { return fwrite($handle, $data); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- streams files of any size in chunks (see the class note) } /** @param resource $handle */ public static function close($handle): bool { return fclose($handle); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- closes a stream opened by open() } /** * Atomic within one filesystem: what file swaps and staged files rely on. False, without trying, where rename() * would fail (and warn): nothing to move, a folder in the way, no write access, or another filesystem. */ public static function rename(string $from, string $to): bool { $exists = file_exists($from) || is_link($from); if (!$exists || !is_dir(dirname($to)) || !wp_is_writable(dirname($to)) || !wp_is_writable(dirname($from)) || (file_exists($to) && (is_dir($to) || is_dir($from))) || lstat($from)['dev'] !== stat(dirname($to))['dev']) { return false; } return rename($from, $to); // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- atomic swap within one filesystem; WP_Filesystem::move() may copy } public static function touch(string $path, int $mtime): bool { return is_file($path) && wp_is_writable($path) && touch($path, $mtime); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_touch -- keeps a copied file's modification time, which "only new or changed" compares } /** What is at $path, without following a symlink: 'link', 'dir', 'file' (any other kind too), or null for nothing. */ public static function kind(string $path): ?string { if (!is_link($path) && !file_exists($path)) { return null; } $stat = lstat($path); if ($stat === false) { return null; } $type = $stat['mode'] & 0170000; return $type === 0120000 ? 'link' : ($type === 0040000 ? 'dir' : 'file'); } /** Whether $path can be written: an existing file that is writable, or a new one in a writable folder. */ public static function writable(string $path): bool { return file_exists($path) ? is_file($path) && wp_is_writable($path) : is_dir(dirname($path)) && wp_is_writable(dirname($path)); } /** * Free space on the disk that holds $dir in bytes, or false when the host doesn't say. * * @return float|false */ public static function free_space(string $dir) { return function_exists('disk_free_space') && is_dir($dir) ? disk_free_space($dir) : false; } /** Send a file to the browser (downloads of exports and backups). */ public static function send(string $path): void { readfile($path); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- streams a download of any size without loading it into memory } /** Send $length bytes of a file from $start (a piece of a download). */ public static function send_range(string $path, int $start, int $length): void { $in = self::open($path, 'rb'); $out = self::open('php://output', 'wb'); if ($in === false || $out === false) { return; } stream_copy_to_stream($in, $out, $length, $start); self::close($in); self::close($out); } private static function failed(string $from, string $to): RelayError { return new RelayError( 'restore.file_swap_failed', /* translators: 1: source path, 2: destination path */ esc_html(sprintf(__('Could not move %1$s to %2$s.', 'siterelay'), $from, $to)), esc_html__('Check the file permissions of wp-content.', 'siterelay') ); } }