← All changes
|
app/Services/FileSystem/Drivers/Local/LocalDriver.php
+20
-8
1.3.23
→
1.6.5
View file →
| @@ -6,8 +6,9 @@ | ||
| 6 | 6 | use FluentCart\App\Helpers\Helper; |
| 7 | 7 | use FluentCart\App\Modules\StorageDrivers\BaseStorageDriver; |
| 8 | 8 | use FluentCart\App\Modules\StorageDrivers\Local\Local as LocalStorageDriver; |
| 9 | 9 | use FluentCart\App\Services\FileSystem\Drivers\BaseDriver; |
| 10 | +use FluentCart\App\Services\FileSystem\StoragePath; | |
| 10 | 11 | use FluentCart\Framework\Support\Arr; |
| 11 | 12 | use FluentCart\Framework\Support\Str; |
| 12 | 13 | |
| 13 | 14 | class LocalDriver extends BaseDriver |
| @@ -186,17 +187,19 @@ | ||
| 186 | 187 | } |
| 187 | 188 | |
| 188 | 189 | public function downloadFile(string $filePath, $fileName = null) |
| 189 | 190 | { |
| 190 | - $filePath = wp_normalize_path( $filePath ); | |
| 191 | 191 | $fileName = sanitize_file_name($fileName); |
| 192 | 192 | |
| 193 | - $file = "{$this->dirPath}/{$filePath}"; | |
| 193 | + $file = $this->getFilePath($filePath); | |
| 194 | 194 | if (ob_get_level()) { |
| 195 | 195 | ob_end_clean(); |
| 196 | 196 | } |
| 197 | 197 | |
| 198 | - if(!file_exists($file)) { | |
| 198 | + // A path that escapes the storage directory reports as missing rather | |
| 199 | + // than as rejected, so this never answers whether a file outside the | |
| 200 | + // directory exists. | |
| 201 | + if (!$file || !file_exists($file)) { | |
| 199 | 202 | return new \WP_Error('file_not_found', __('File not found', 'fluent-cart')); |
| 200 | 203 | } |
| 201 | 204 | $fileSize = filesize($file); |
| 202 | 205 | $fileName = $fileName ?? basename($filePath); |
| @@ -211,11 +214,19 @@ | ||
| 211 | 214 | } |
| 212 | 215 | exit; |
| 213 | 216 | } |
| 214 | 217 | |
| 218 | + /** | |
| 219 | + * Absolute path for a stored file, or '' when $filePath escapes the | |
| 220 | + * storage directory. | |
| 221 | + * | |
| 222 | + * `..` survives both sanitize_text_field() and wp_normalize_path(), so | |
| 223 | + * composing the path by concatenation alone let a relative path address any | |
| 224 | + * file the web user could reach. | |
| 225 | + */ | |
| 215 | 226 | public function getFilePath(string $filePath, $fileName = null): string |
| 216 | 227 | { |
| 217 | - return "{$this->dirPath}/{$filePath}"; | |
| 228 | + return StoragePath::contain($this->dirPath, $filePath); | |
| 218 | 229 | } |
| 219 | 230 | |
| 220 | 231 | protected function retrieveFileForDownload(string $downloadableFilePath) |
| 221 | 232 | { |
| @@ -228,15 +239,16 @@ | ||
| 228 | 239 | if ( !current_user_can('manage_options') ) { |
| 229 | 240 | return new \WP_Error('permission_error', __('You are not allowed to delete file', 'fluent-cart')); |
| 230 | 241 | } |
| 231 | 242 | |
| 232 | - $filePath = wp_normalize_path($filePath); | |
| 233 | 243 | $fullPath = $this->getFilePath($filePath); |
| 234 | - | |
| 235 | - if (!file_exists($fullPath)) { | |
| 244 | + | |
| 245 | + // Same as the download path: an escaping path reports as missing so the | |
| 246 | + // response cannot be used to probe for files outside the directory. | |
| 247 | + if (!$fullPath || !file_exists($fullPath)) { | |
| 236 | 248 | return new \WP_Error('file_not_found', __('File not found', 'fluent-cart')); |
| 237 | 249 | } |
| 238 | - | |
| 250 | + | |
| 239 | 251 | if (!is_file($fullPath)) { |
| 240 | 252 | return new \WP_Error('not_a_file', __('Path is not a file', 'fluent-cart')); |
| 241 | 253 | } |
| 242 | 254 | |