PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / -3.0.1
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v-3.0.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 2.10.2 All 137 releases
bit-form / includes / API / Controller / FileController.php

FileController.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder -3.0.1, at includes/API/Controller/FileController.php

148 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace BitCode\BitForm\API\Controller;
4
5 use BitCode\BitForm\Core\Database\FormEntryModel;
6 use BitCode\BitForm\Core\Util\FileHandler;
7 use WP_Error;
8 use WP_REST_Controller;
9 use WP_REST_Request;
10
11 class FileController extends WP_REST_Controller
12 {
13 public function handleFileDownload(WP_REST_Request $request)
14 {
15 $params = $request->get_params();
16 $formID = isset($params['formID']) ? absint($params['formID']) : 0;
17 if (empty($formID)) {
18 return new WP_Error('invalid_form_id', 'Invalid or missing formID parameter', ['status' => 400, 'success' => false]);
19 }
20
21 $entryID = isset($params['entryID']) ? absint($params['entryID']) : 0;
22 if (empty($entryID)) {
23 return new WP_Error('invalid_entry_id', 'Invalid or missing entryID parameter', ['status' => 400, 'success' => false]);
24 }
25
26 $fileName = isset($params['fileID']) ? sanitize_file_name((string) $params['fileID']) : '';
27 if (empty($fileName)) {
28 return new WP_Error('invalid_file_id', 'Invalid or missing fileID parameter', ['status' => 400, 'success' => false]);
29 }
30
31 $filePath = $this->getValidatedFilePath($formID, $entryID, $fileName);
32 if (is_wp_error($filePath)) {
33 return $filePath;
34 }
35
36 if (!$this->isValidEntryFileRequest($formID, $entryID)) {
37 return new WP_Error('file_not_found', 'File not found.', ['status' => 404, 'success' => false]);
38 }
39
40 $forceDownload = isset($params['download']) && 'true' === strtolower(sanitize_text_field((string) $params['download']));
41
42 $this->serveFile($filePath, $forceDownload);
43 }
44
45 private function getValidatedFilePath($formID, $entryID, $fileName)
46 {
47 $baseDir = FileHandler::getEntriesFileUploadDir($formID, $entryID);
48 $baseDirRealPath = realpath($baseDir);
49
50 if (empty($baseDirRealPath) || !is_dir($baseDirRealPath)) {
51 return new WP_Error('file_not_found', 'File not found.', ['status' => 404, 'success' => false]);
52 }
53
54 $filePath = $baseDirRealPath . DIRECTORY_SEPARATOR . $fileName;
55 $realFilePath = realpath($filePath);
56
57 if (empty($realFilePath) || !is_file($realFilePath) || !is_readable($realFilePath)) {
58 return new WP_Error('file_not_found', 'File not found.', ['status' => 404, 'success' => false]);
59 }
60
61 $normalizedBaseDir = trailingslashit(wp_normalize_path($baseDirRealPath));
62 $normalizedFilePath = wp_normalize_path($realFilePath);
63
64 if (0 !== strpos($normalizedFilePath, $normalizedBaseDir)) {
65 return new WP_Error('invalid_file_path', 'Invalid file path.', ['status' => 400, 'success' => false]);
66 }
67
68 return $realFilePath;
69 }
70
71 private function isValidEntryFileRequest($formID, $entryID)
72 {
73 $entryModel = new FormEntryModel();
74 $entry = $entryModel->get(
75 'id',
76 [
77 'id' => $entryID,
78 'form_id' => $formID,
79 ]
80 );
81
82 return !is_wp_error($entry) && !empty($entry);
83 }
84
85 private function serveFile($filePath, $forceDownload = false)
86 {
87 $fileName = wp_basename($filePath);
88 $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
89 $fileInfo = wp_check_filetype($fileName);
90 $contentType = !empty($fileInfo['type']) ? $fileInfo['type'] : 'application/octet-stream';
91
92 $safeTextPreviewExtensions = [
93 'csv',
94 'json',
95 'md',
96 'txt',
97 ];
98 $safeMediaPreviewExtensions = [
99 'pdf',
100 'jpg',
101 'jpeg',
102 'png',
103 'gif',
104 'webp',
105 ];
106 $allowInlineTextPreview = !$forceDownload && in_array($extension, $safeTextPreviewExtensions, true);
107 $allowInlineMediaPreview = !$forceDownload && in_array($extension, $safeMediaPreviewExtensions, true);
108 $allowInlinePreview = $allowInlineTextPreview || $allowInlineMediaPreview;
109
110 if ($allowInlineTextPreview) {
111 $contentType = 'text/plain; charset=' . get_option('blog_charset', 'UTF-8');
112 } elseif (!$allowInlineMediaPreview) {
113 $contentType = 'application/octet-stream';
114 }
115
116 if (!headers_sent()) {
117 if (function_exists('nocache_headers')) {
118 nocache_headers();
119 }
120
121 header('X-Content-Type-Options: nosniff');
122 header('X-Frame-Options: SAMEORIGIN');
123 header('Referrer-Policy: same-origin');
124 header('Content-Description: File Transfer');
125 header('Content-Length: ' . filesize($filePath));
126 header('Content-Transfer-Encoding: binary');
127 header('Accept-Ranges: none');
128
129 if (!$allowInlinePreview) {
130 header('Content-Type: application/octet-stream');
131 header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"; filename*=UTF-8\'\'' . rawurlencode($fileName));
132 } else {
133 header('Content-Type: ' . $contentType);
134 header('Content-Disposition: inline; filename="' . rawurlencode($fileName) . '"; filename*=UTF-8\'\'' . rawurlencode($fileName));
135 }
136 }
137
138 while (ob_get_level() > 0) {
139 ob_end_clean();
140 }
141
142 flush();
143 // 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.
144 readfile($filePath);
145 exit;
146 }
147 }
148