PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / trunk
Kirki – Freeform Page Builder, Website Builder & Customizer vtrunk
6.3.0 6.2.5 6.2.4 6.2.3 6.2.2 6.2.1 6.2.0 6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / app / Supports / FileHandler.php
kirki / app / Supports Last commit date
Facades 1 month ago Form 3 weeks ago ActionHooks.php 1 month ago Canvas.php 1 month ago CollectionItem.php 1 month ago ContentManager.php 1 month ago DateTime.php 1 month ago EditorPreview.php 1 month ago FileHandler.php 21 hours ago FilterHooks.php 1 month ago PageUrl.php 1 month ago Recaptcha.php 1 week ago Role.php 1 month ago Session.php 1 month ago Template.php 1 month ago
FileHandler.php
178 lines
1 <?php
2
3 namespace Kirki\App\Supports;
4
5 defined('ABSPATH') || exit;
6
7 use Exception;
8 use Kirki\Framework\Supports\Facades\File as FileHelper;
9 use Kirki\Framework\Supports\Facades\Http;
10 use Kirki\HelperFunctions;
11 use PclZip;
12
13 use function Kirki\App\get_upload_directory;
14 use function Kirki\Framework\clean_path;
15
16 class FileHandler
17 {
18 public static function get_temp_folder_path()
19 {
20 $temp_folder = 'kirki_temp';
21
22 return get_upload_directory() . '/' . $temp_folder;
23 }
24
25 /**
26 * Download zip file from remote server
27 *
28 * @param string $remote_file_url
29 * @param string $file_name
30 * @return string|false -- if failed return false
31 */
32 public static function download_zip_from_remote(string $remote_file_url, string $file_name)
33 {
34 // Extension check must run against the URL *path* only, not the whole
35 // URL — otherwise "?x=.zip" trivially satisfies a whole-string check.
36 $url_path = (string) wp_parse_url($remote_file_url, PHP_URL_PATH);
37 $file_ext = strtolower(pathinfo($url_path, PATHINFO_EXTENSION));
38 $allowed = ['zip'];
39
40 if (!in_array($file_ext, $allowed, true)) {
41 return false;
42 }
43
44 if (!static::is_remote_host_allowed($remote_file_url)) {
45 return false;
46 }
47
48 // Download the file from the remote server.
49 $response = Http::timeout(120)
50 ->with_options([
51 'redirection' => 0
52 ])
53 ->with_user_agent('WordPress')
54 ->get($remote_file_url);
55
56 if ($response->failed()) {
57 return false;
58 }
59
60 // Save the file locally.
61 // Local path to save the downloaded file.
62 $local_file_path = clean_path(get_upload_directory() . '/' . $file_name, false);
63
64 static::verify_directory_traversal($local_file_path);
65
66 $is_downloaded = FileHelper::put($local_file_path, $response->body());
67
68 if (!$is_downloaded) {
69 return false;
70 }
71
72 return $local_file_path;
73 }
74
75 /**
76 * Same-site URLs are always allowed (e.g. dev config points the apps base
77 * URL at content_url() on the site's own — sometimes private/loopback —
78 * host). Any other host must resolve to a public address, so a remote zip
79 * URL can't be used to probe the server's own internal network.
80 *
81 * @param string $url
82 * @return bool
83 */
84 private static function is_remote_host_allowed(string $url)
85 {
86 $host = wp_parse_url($url, PHP_URL_HOST);
87
88 if (!is_string($host) || $host === '') {
89 return false;
90 }
91
92 $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
93
94 if (is_string($site_host) && strcasecmp($host, $site_host) === 0) {
95 return true;
96 }
97
98 return HelperFunctions::is_safe_url($url);
99 }
100
101 /**
102 * @return array|false
103 * return false on failure
104 */
105 public static function extract_zip_file(string $zip_file_path, string $destination_dir)
106 {
107 if (!class_exists('PclZip')) {
108 require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
109 }
110
111 $zip_file_path = clean_path($zip_file_path, false);
112
113 if (FileHelper::missing($zip_file_path)) {
114 return false;
115 }
116
117 if (!FileHelper::is_directory($destination_dir)) {
118 FileHelper::make_dir($destination_dir);
119 }
120
121 $zip = new PclZip($zip_file_path);
122
123 static::validate_zip_file($zip);
124
125 $result = $zip->extract(
126 PCLZIP_OPT_PATH,
127 $destination_dir
128 );
129
130 if (is_array($result)) {
131 return $result;
132 }
133
134 return false;
135 }
136
137 public static function validate_zip_file(PclZip $zip)
138 {
139 $list = $zip->listContent();
140
141 if (!is_array($list)) {
142 throw new Exception(esc_html__('Failed to read ZIP file.', 'kirki'));
143 }
144
145 foreach ($list as $entry) {
146 if (!isset($entry['filename'])) {
147 throw new Exception(esc_html__('Invalid ZIP file.', 'kirki'));
148 }
149
150 static::validate_zip_entry($entry['filename']);
151 }
152 }
153
154 private static function validate_zip_entry(string $entry_filename)
155 {
156 $entry_filename = clean_path($entry_filename, false);
157
158 if (
159 $entry_filename === '' ||
160 str_contains($entry_filename, "\0") ||
161 str_starts_with($entry_filename, '/') ||
162 preg_match('/^[A-Za-z]:\//', $entry_filename)
163 ) {
164 throw new Exception(esc_html__('Invalid ZIP file.', 'kirki'));
165 }
166
167 return static::verify_directory_traversal($entry_filename);
168 }
169
170 Public static function verify_directory_traversal(string $path) {
171 if (preg_match('#(^|/)\.\.(/|$)#', clean_path($path, false))) {
172 /* translators: %s: File Path */
173 throw new Exception(sprintf(esc_html__('Directory traversal detected in %s.', 'kirki'), $path));
174 }
175
176 return true;
177 }
178 }