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/FileDownloadProvider.php +65 -9 2.10.03.3.1 View file →
@@ -1,10 +1,51 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Core\Util;
4 4
5 +if (!defined('ABSPATH')) {
6 + exit;
7 +}
8 +
5 9 final class FileDownloadProvider
6 10 {
11 + private function isAuthorizedFileRequest($formID, $entryID)
12 + {
13 + if (!is_user_logged_in()) {
14 + return false;
15 + }
16 +
17 + $currentUserId = get_current_user_id();
18 + if (empty($currentUserId)) {
19 + return false;
20 + }
21 +
22 + // If a nonce is provided, verify it. If it's missing/invalid, fall back to an ownership/capability check.
23 + $nonce = isset($_GET['nonce']) && is_scalar($_GET['nonce']) ? sanitize_text_field(wp_unslash((string) $_GET['nonce'])) : '';
24 + $nonceAction = 'bitforms_file_download_' . $formID . '_' . $entryID;
25 + if (!empty($nonce) && wp_verify_nonce($nonce, $nonceAction)) {
26 + return true;
27 + }
28 +
29 + // Capability bypass.
30 + if (current_user_can('manage_bitform') || current_user_can('manage_options')) {
31 + return true;
32 + }
33 +
34 + // Ownership check: non-admin users may only download their own entry files.
35 + $entryModel = new \BitCode\BitForm\Core\Database\FormEntryModel();
36 + $entry = $entryModel->get(
37 + 'id',
38 + [
39 + 'id' => $entryID,
40 + 'form_id' => $formID,
41 + 'user_id' => $currentUserId,
42 + ]
43 + );
44 +
45 + return !is_wp_error($entry) && !empty($entry);
46 + }
47 +
7 48 public function register()
8 49 {
9 50 add_action('template_redirect', [$this, 'authCheckandFrceDownloadHelper']);
10 51 add_shortcode('bitforms-frontend-file', [$this, 'handleFileDownload']);
@@ -11,8 +52,9 @@
11 52 }
12 53
13 54 public function handleFileDownload()
14 55 {
56 + // File download: form/entry/file IDs read from query string; authorization enforced via isAuthorizedFileRequest() below.
15 57 if (!isset($_GET['formID']) || !isset($_GET['entryID']) || !isset($_GET['fileID'])) {
16 58 global $wp_query;
17 59 $wp_query->set_404();
18 60 status_header(404);
@@ -18,14 +60,21 @@
18 60 status_header(404);
19 61 get_template_part(404);
20 62 exit();
21 63 }
22 - $formID = intval(sanitize_text_field($_GET['formID']));
23 - $entryID = intval(sanitize_text_field($_GET['entryID']));
24 - $fileID = sanitize_file_name($_GET['fileID']);
25 - $filePath = BITFORMS_UPLOAD_DIR . DIRECTORY_SEPARATOR . $formID . DIRECTORY_SEPARATOR . $entryID . DIRECTORY_SEPARATOR . $fileID;
64 + $formID = intval(sanitize_text_field(wp_unslash($_GET['formID'])));
65 + $entryID = intval(sanitize_text_field(wp_unslash($_GET['entryID'])));
66 + $fileID = sanitize_file_name(wp_unslash($_GET['fileID']));
67 + if (!$this->isAuthorizedFileRequest($formID, $entryID)) {
68 + $this->show404();
69 + }
70 +
71 + $filePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR . $fileID;
72 +
26 73 if (is_readable($filePath)) {
27 74 $this->fileDownloadORView($filePath, true);
75 + } else {
76 + $this->show404();
28 77 }
29 78 }
30 79
31 80 public static function getBaseDownloadURL()
@@ -102,15 +151,21 @@
102 151 }
103 152
104 153 private function isRequestedFileExists()
105 154 {
155 + // File download: IDs read from query string; authorization enforced via isAuthorizedFileRequest() below.
106 156 if (!isset($_GET['formID']) || !isset($_GET['entryID']) || !isset($_GET['fileID'])) {
107 157 return false;
108 158 }
109 - $formID = intval(sanitize_text_field($_GET['formID']));
110 - $entryID = intval(sanitize_text_field($_GET['entryID']));
111 - $fileID = sanitize_file_name($_GET['fileID']);
112 - $filePath = BITFORMS_UPLOAD_DIR . DIRECTORY_SEPARATOR . $formID . DIRECTORY_SEPARATOR . $entryID . DIRECTORY_SEPARATOR . $fileID;
159 + $formID = intval(sanitize_text_field(wp_unslash($_GET['formID'])));
160 + $entryID = intval(sanitize_text_field(wp_unslash($_GET['entryID'])));
161 + $fileID = sanitize_file_name(wp_unslash($_GET['fileID']));
162 +
163 + if (!$this->isAuthorizedFileRequest($formID, $entryID)) {
164 + return false;
165 + }
166 +
167 + $filePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR . $fileID;
113 168 if (is_readable($filePath)) {
114 169 return $filePath;
115 170 }
116 171
@@ -129,9 +184,9 @@
129 184 $content_types = 'text/plain';
130 185 if ($fileInfo['type'] && $fileInfo['ext']) {
131 186 $content_types = $fileInfo['type'];
132 187 $ext = $fileInfo['ext'];
133 - if (in_array($ext[1], ['txt', 'php', 'html', 'xhtml', 'json'])) {
188 + if (in_array($ext, ['txt', 'php', 'html', 'xhtml', 'json'], true)) {
134 189 $content_types = 'text/plain';
135 190 }
136 191 }
137 192 header('Content-Disposition:filename="' . basename($filePath) . '"');
@@ -143,8 +198,9 @@
143 198 header('Pragma: public');
144 199 header('Content-Length: ' . filesize($filePath));
145 200 header('Content-Transfer-Encoding: binary ');
146 201 flush();
202 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Streaming binary download; WP_Filesystem has no streaming equivalent and get_contents() would load entire file into memory.
147 203 readfile($filePath);
148 204 die();
149 205 }
150 206 }