| @@ -142,8 +142,9 @@ | ||
| 142 | 142 | } |
| 143 | 143 | |
| 144 | 144 | public function store(int $userID, array $fields, bool $ajax = false): void |
| 145 | 145 | { |
| 146 | + $uploadedFileFields = []; | |
| 146 | 147 | $customField = acym_getVar('array', 'customField', [], 'FILES'); |
| 147 | 148 | if (!empty($customField['tmp_name'])) { |
| 148 | 149 | $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS; |
| 149 | 150 | $uploadPath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$userID.DS); |
| @@ -160,10 +161,11 @@ | ||
| 160 | 161 | $fileName = $customField['name'][$key]; |
| 161 | 162 | while (is_array($fileName) && isset($fileName[0])) { |
| 162 | 163 | $fileName = $fileName[0]; |
| 163 | 164 | } |
| 165 | + $fileName = basename($fileName); | |
| 164 | 166 | |
| 165 | - if (!preg_match('#\.('.implode('|', $allowedExtensions).')$#Ui', $fileName)) { | |
| 167 | + if (!acym_fileNameValid($fileName) || !preg_match('#\.('.implode('|', array_map('preg_quote', $allowedExtensions)).')$#i', $fileName)) { | |
| 166 | 168 | $ext = substr($fileName, strrpos($fileName, '.') + 1); |
| 167 | 169 | if ($ajax) { |
| 168 | 170 | $this->errors[] = acym_translationSprintf( |
| 169 | 171 | 'ACYM_ACCEPTED_TYPE', |
| @@ -184,9 +186,9 @@ | ||
| 184 | 186 | |
| 185 | 187 | continue; |
| 186 | 188 | } |
| 187 | 189 | |
| 188 | - if (!acym_uploadFile($value, $uploadPath.$fileName)) { | |
| 190 | + if (!acym_isPathWithinDirectory($uploadPath, $uploadPath.$fileName) || !acym_uploadFile($value, $uploadPath.$fileName)) { | |
| 189 | 191 | if ($ajax) { |
| 190 | 192 | $this->errors[] = acym_translation('ACYM_ERROR_SAVING'); |
| 191 | 193 | } else { |
| 192 | 194 | acym_enqueueMessage(acym_translation('ACYM_ERROR_SAVING'), 'error'); |
| @@ -193,9 +195,10 @@ | ||
| 193 | 195 | } |
| 194 | 196 | |
| 195 | 197 | continue; |
| 196 | 198 | } |
| 197 | - $fields[$key] = $customField['name'][$key]; | |
| 199 | + $fields[$key] = $fileName; | |
| 200 | + $uploadedFileFields[$key] = true; | |
| 198 | 201 | } |
| 199 | 202 | } |
| 200 | 203 | |
| 201 | 204 | if (empty($fields)) return; |
| @@ -248,8 +251,13 @@ | ||
| 248 | 251 | if (in_array($field->type, ['text', 'textarea']) && !empty($fieldOptions->max_characters)) { |
| 249 | 252 | $value = substr($value, 0, $fieldOptions->max_characters); |
| 250 | 253 | } |
| 251 | 254 | |
| 255 | + // A file field value can only be set through an actual upload, never through a plain posted string | |
| 256 | + if ($field->type === 'file' && strlen($value) !== 0 && empty($uploadedFileFields[$id])) { | |
| 257 | + continue; | |
| 258 | + } | |
| 259 | + | |
| 252 | 260 | // If deleting a file field, also delete the physical file |
| 253 | 261 | if ($field->type === 'file' && strlen($value) === 0) { |
| 254 | 262 | $oldValue = acym_loadResult( |
| 255 | 263 | 'SELECT `value` FROM #__acym_user_has_field WHERE `user_id` = '.intval($userID).' AND `field_id` = '.intval($id) |
| @@ -258,15 +266,21 @@ | ||
| 258 | 266 | $fileName = @json_decode($oldValue, true); |
| 259 | 267 | if (is_array($fileName)) $fileName = $fileName[0]; |
| 260 | 268 | if (empty($fileName)) $fileName = $oldValue; |
| 261 | 269 | |
| 262 | - $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS; | |
| 263 | - // Try new path (with user folder) first, then legacy path (without user folder) | |
| 264 | - $filePath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$userID.DS.$fileName); | |
| 265 | - if (!file_exists($filePath)) { | |
| 266 | - $filePath = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS.$fileName); | |
| 270 | + $fileName = basename($fileName); | |
| 271 | + if (acym_fileNameValid($fileName)) { | |
| 272 | + $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS; | |
| 273 | + $userfilesBase = acym_cleanPath(ACYM_ROOT.$uploadFolder.'userfiles'.DS); | |
| 274 | + // Try new path (with user folder) first, then legacy path (without user folder) | |
| 275 | + $filePath = acym_cleanPath($userfilesBase.$userID.DS.$fileName); | |
| 276 | + if (!file_exists($filePath)) { | |
| 277 | + $filePath = acym_cleanPath($userfilesBase.$fileName); | |
| 278 | + } | |
| 279 | + if (acym_isPathWithinDirectory($userfilesBase, $filePath)) { | |
| 280 | + acym_deleteFile($filePath); | |
| 281 | + } | |
| 267 | 282 | } |
| 268 | - acym_deleteFile($filePath); | |
| 269 | 283 | } |
| 270 | 284 | } |
| 271 | 285 | |
| 272 | 286 | // The user removed a value, don't add an empty line and remove any previous value from the bd |
| @@ -561,8 +575,19 @@ | ||
| 561 | 575 | : json_encode($defaultValue) |
| 562 | 576 | ); |
| 563 | 577 | } |
| 564 | 578 | |
| 579 | + $allowedFieldNameTags = array_merge(SecurityHelper::ALLOWED_HTML_FIELD_NAME, [ | |
| 580 | + 'a' => [ | |
| 581 | + 'href' => true, | |
| 582 | + 'target' => true, | |
| 583 | + 'title' => true, | |
| 584 | + 'class' => true, | |
| 585 | + 'rel' => true, | |
| 586 | + 'aria-label' => true, | |
| 587 | + ], | |
| 588 | + ]); | |
| 589 | + | |
| 565 | 590 | $labelTypes = ['text', 'textarea', 'single_dropdown', 'multiple_dropdown', 'custom_text', 'language']; |
| 566 | 591 | $titleTypes = ['date', 'radio', 'checkbox', 'file']; |
| 567 | 592 | |
| 568 | 593 | $useLabel = $displayOutside && (in_array($field->id, [1, 2]) || in_array($field->type, $labelTypes)); |
| @@ -579,14 +604,14 @@ | ||
| 579 | 604 | $groupLabelId = 'acym__field__label__'.intval($field->id).'_'.acym_rand(1000, 9999); |
| 580 | 605 | echo '<div class="cell margin-top-1" role="group" aria-labelledby="'.esc_attr($groupLabelId).'"'.($field->required ? ' aria-required="true"' : '').'>'; |
| 581 | 606 | echo '<div id="'.esc_attr($groupLabelId).'" class="acym__users__creation__fields__title">'.wp_kses( |
| 582 | 607 | $field->name, |
| 583 | - SecurityHelper::ALLOWED_HTML_FIELD_NAME | |
| 608 | + $allowedFieldNameTags | |
| 584 | 609 | ).'</div>'; |
| 585 | 610 | } else { |
| 586 | 611 | echo '<div class="cell margin-top-1"><div class="acym__users__creation__fields__title">'.wp_kses( |
| 587 | 612 | $field->name, |
| 588 | - SecurityHelper::ALLOWED_HTML_FIELD_NAME | |
| 613 | + $allowedFieldNameTags | |
| 589 | 614 | ).'</div>'; |
| 590 | 615 | } |
| 591 | 616 | } |
| 592 | 617 | |
| @@ -799,9 +824,10 @@ | ||
| 799 | 824 | } else { |
| 800 | 825 | $downloadUrl = ''; |
| 801 | 826 | if (!empty($defaultValue) && !empty($user->id)) { |
| 802 | 827 | $fileName = is_array($defaultValue) ? $defaultValue[0] : $defaultValue; |
| 803 | - if (!empty($fileName)) { | |
| 828 | + $fileName = basename($fileName); | |
| 829 | + if (!empty($fileName) && acym_fileNameValid($fileName)) { | |
| 804 | 830 | $uploadFolder = trim(acym_cleanPath(html_entity_decode(acym_getFilesFolder(true))), DS.' ').DS; |
| 805 | 831 | $userFilePath = $uploadFolder.'userfiles'.DS.$user->id.DS.$fileName; |
| 806 | 832 | $legacyFilePath = $uploadFolder.'userfiles'.DS.$fileName; |
| 807 | 833 | |