PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.3.1
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.3.1
3.3.1 V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 All 138 releases
← All changes | includes/Core/Util/FileHandler.php +115 -17 3.1.03.3.1 View file →
@@ -121,14 +121,60 @@
121 121 }
122 122 return $file_upoalded;
123 123 }
124 124
125 + public static function isSafeFileName($name)
126 + {
127 + if (!is_string($name)) {
128 + return false;
129 + }
130 +
131 + $trimmed = trim($name);
132 + if ('' === $trimmed) {
133 + return false;
134 + }
135 +
136 + $baseName = basename($trimmed);
137 + if ('' === $baseName || $baseName !== $trimmed || 'index.php' === $baseName) {
138 + return false;
139 + }
140 +
141 + return sanitize_file_name($trimmed) === $trimmed;
142 + }
143 +
125 144 public function deleteFiles($form_id, $entry_id, $files)
126 145 {
127 146 $_upload_dir = self::getEntriesFileUploadDir($form_id, $entry_id);
128 - foreach ($files as $name) {
129 - wp_delete_file($_upload_dir . DIRECTORY_SEPARATOR . $name);
147 + $resolvedBitformsUploadDir = realpath(BITFORMS_UPLOAD_DIR);
148 + $resolvedUploadDir = realpath($_upload_dir);
149 + if (false === $resolvedBitformsUploadDir || false === $resolvedUploadDir) {
150 + return;
130 151 }
152 +
153 + $bitformsUploadDirPrefix = trailingslashit(wp_normalize_path($resolvedBitformsUploadDir));
154 + $uploadDirPrefix = trailingslashit(wp_normalize_path($resolvedUploadDir));
155 + if (0 !== strpos($uploadDirPrefix, $bitformsUploadDirPrefix)) {
156 + return;
157 + }
158 +
159 + foreach ((array) $files as $name) {
160 + if (!self::isSafeFileName($name)) {
161 + continue;
162 + }
163 +
164 + $candidatePath = $resolvedUploadDir . DIRECTORY_SEPARATOR . $name;
165 + $resolvedPath = realpath($candidatePath);
166 + if (false === $resolvedPath || !is_file($resolvedPath)) {
167 + continue;
168 + }
169 +
170 + $normalizedPath = wp_normalize_path($resolvedPath);
171 + if (0 !== strpos($normalizedPath, $uploadDirPrefix)) {
172 + continue;
173 + }
174 +
175 + wp_delete_file($resolvedPath);
176 + }
131 177 }
132 178
133 179 public static function getFileUploadError($code)
134 180 {
@@ -188,19 +234,23 @@
188 234 $tempDir = $upload_dir['basedir'] . '/bitforms/temp';
189 235 $destinationDir = self::getEntriesFileUploadDir($formId, $entryID) . DIRECTORY_SEPARATOR;
190 236 self::createIndexFile($destinationDir);
191 237
238 + $consumedFiles = [];
239 +
192 240 foreach ($submitted_data as $key => $data) {
193 241 if (isset($fields[$key]) && 'advanced-file-up' === $fields[$key]['type']) {
194 - $files = $data;
195 242 $fldData = $submitted_data[$key];
196 - $files = explode(',', $fldData);
197 - if (is_array($files) && count($files) > 0) {
198 - foreach ($files as $file) {
199 - self::fileCopy($tempDir, $destinationDir, trim($file));
243 + // A repeater row (or a pre-split value) hands this field over as an array; explode()
244 + // on an array is a TypeError on PHP 8, which would fatal mid-submission.
245 + $files = \is_array($fldData) ? $fldData : explode(',', \is_scalar($fldData) ? (string) $fldData : '');
246 + foreach ($files as $file) {
247 + $safeFile = \is_scalar($file) ? trim((string) $file) : '';
248 + if ('' === $safeFile) {
249 + continue;
200 250 }
201 - } else {
202 - self::fileCopy($tempDir, $destinationDir, trim($files));
251 + self::fileCopy($tempDir, $destinationDir, $safeFile);
252 + $consumedFiles[] = $safeFile;
203 253 }
204 254 if (!empty($files)) {
205 255 $submitted_data[$key] = $files;
206 256 }
@@ -205,20 +255,68 @@
205 255 $submitted_data[$key] = $files;
206 256 }
207 257 }
208 258 }
259 +
260 + self::cleanupTempUploads($tempDir, $consumedFiles);
261 +
262 + return $submitted_data;
263 + }
264 +
265 + /**
266 + * Clear the shared temp upload staging area after a submission has taken what it needs.
267 + *
268 + * @param string $tempDir shared staging directory
269 + * @param array $consumedFiles file names copied into the entry directory by this submission
270 + *
271 + * @return void
272 + */
273 + private static function cleanupTempUploads($tempDir, array $consumedFiles)
274 + {
209 275 $tempBase = realpath($tempDir);
210 - if (false !== $tempBase) {
211 - $tmpFiles = glob($tempBase . DIRECTORY_SEPARATOR . '*');
212 - foreach ((array) $tmpFiles as $tmpFile) {
213 - $resolved = realpath($tmpFile);
214 - if (false !== $resolved && 0 === strpos($resolved, $tempBase . DIRECTORY_SEPARATOR)) {
215 - wp_delete_file($resolved);
216 - }
276 + if (false === $tempBase) {
277 + return;
278 + }
279 + $boundary = $tempBase . DIRECTORY_SEPARATOR;
280 +
281 + // Never remove the directory-hardening files the uploads dir relies on.
282 + $protected = ['index.php', 'index.html', '.htaccess'];
283 +
284 + foreach ($consumedFiles as $file) {
285 + $resolved = realpath($boundary . $file);
286 + if (false === $resolved || 0 !== strpos($resolved, $boundary) || !is_file($resolved)) {
287 + continue;
217 288 }
289 + if (\in_array(basename($resolved), $protected, true)) {
290 + continue;
291 + }
292 + wp_delete_file($resolved);
218 293 }
219 294
220 - return $submitted_data;
295 + // Sweep abandoned uploads. Anything still here after the retention window belongs to a form
296 + // that was never submitted. Filterable so a site with very long multi-step forms can extend
297 + // it; 0 or less disables the sweep entirely.
298 + $retention = apply_filters('bitform_temp_upload_retention', DAY_IN_SECONDS);
299 + $retention = is_numeric($retention) ? (int) $retention : DAY_IN_SECONDS;
300 + if ($retention <= 0) {
301 + return;
302 + }
303 +
304 + $cutoff = time() - $retention;
305 + $tmpFiles = glob($boundary . '*');
306 + foreach ((array) $tmpFiles as $tmpFile) {
307 + $resolved = realpath($tmpFile);
308 + if (false === $resolved || 0 !== strpos($resolved, $boundary) || !is_file($resolved)) {
309 + continue;
310 + }
311 + if (\in_array(basename($resolved), $protected, true)) {
312 + continue;
313 + }
314 + $modified = @filemtime($resolved);
315 + if (false !== $modified && $modified < $cutoff) {
316 + wp_delete_file($resolved);
317 + }
318 + }
221 319 }
222 320
223 321 private function getByteSizeByUnit($sizeString)
224 322 {