Allow uploads of files containing dots in filename. Closes BookStackApp/BookStack#2217

This commit is contained in:
Timo Förster 2021-03-04 21:45:56 +01:00
parent 4d4a57d1bf
commit 745d15d200
No known key found for this signature in database
GPG Key ID: 02B35F5A55142661
35 changed files with 25 additions and 42 deletions

View File

@ -159,6 +159,6 @@ abstract class Controller extends BaseController
*/
protected function getImageValidationRules(): string
{
return 'image_extension|no_double_extension|mimes:jpeg,png,gif,webp';
return 'image_extension|mimes:jpeg,png,gif,webp';
}
}

View File

@ -18,11 +18,6 @@ class CustomValidationServiceProvider extends ServiceProvider
return in_array(strtolower($value->getClientOriginalExtension()), $validImageExtensions);
});
Validator::extend('no_double_extension', function ($attribute, $value, $parameters, $validator) {
$uploadName = $value->getClientOriginalName();
return substr_count($uploadName, '.') < 2;
});
Validator::extend('safe_url', function ($attribute, $value, $parameters, $validator) {
$cleanLinkName = strtolower(trim($value));
$isJs = strpos($cleanLinkName, 'javascript:') === 0;

View File

@ -60,7 +60,7 @@ class ImageService
int $resizeHeight = null,
bool $keepRatio = true
) {
$imageName = $uploadedFile->getClientOriginalName();
$imageName = $this->sanitizeFileName($uploadedFile->getClientOriginalName());
$imageData = file_get_contents($uploadedFile->getRealPath());
if ($resizeWidth !== null || $resizeHeight !== null) {
@ -426,4 +426,15 @@ class ImageService
$basePath = ($this->storageUrl == false) ? url('/') : $this->storageUrl;
return rtrim($basePath, '/') . $filePath;
}
/**
* Returns a sanitized filename with only one file extension
*/
private function sanitizeFileName(string $fileName): string
{
$parts = explode('.', $fileName);
$extension = array_pop($parts);
return sprintf('%s.%s', implode('-', $parts), $extension);
}
}

View File

@ -78,7 +78,6 @@ return [
'string' => 'يجب أن يكون :attribute على الأقل :min حرف / حروف.',
'array' => 'يجب أن يحتوي :attribute على :min عنصر / عناصر كحد أدنى.',
],
'no_double_extension' => 'يجب أن يكون للسمة: امتداد ملف واحد فقط.',
'not_in' => ':attribute المحدد غير صالح.',
'not_regex' => 'صيغة السمة: غير صالحة.',
'numeric' => 'يجب أن يكون :attribute رقم.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute musí být delší než :min znaků.',
'array' => ':attribute musí obsahovat více než :min prvků.',
],
'no_double_extension' => ':attribute musí obsahovat pouze jednu příponu souboru.',
'not_in' => 'Zvolená hodnota pro :attribute je neplatná.',
'not_regex' => ':attribute musí být regulární výraz.',
'numeric' => ':attribute musí být číslo.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute skal mindst være :min tegn.',
'array' => ':attribute skal have mindst :min elementer.',
],
'no_double_extension' => ':attribute må kun indeholde én filtype.',
'not_in' => 'Den valgte :attribute er ikke gyldig.',
'not_regex' => ':attribute-formatet er ugyldigt.',
'numeric' => ':attribute skal være et tal.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute muss mindestens :min Zeichen lang sein.',
'array' => ':attribute muss mindesten :min Elemente enthalten.',
],
'no_double_extension' => ':attribute darf nur eine gültige Dateiendung',
'not_in' => ':attribute ist ungültig.',
'not_regex' => ':attribute ist kein valides Format.',
'numeric' => ':attribute muss eine Zahl sein.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute muss mindestens :min Zeichen lang sein.',
'array' => ':attribute muss mindesten :min Elemente enthalten.',
],
'no_double_extension' => ':attribute darf nur eine gültige Dateiendung',
'not_in' => ':attribute ist ungültig.',
'not_regex' => ':attribute ist kein gültiges Format.',
'numeric' => ':attribute muss eine Zahl sein.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'El :attribute debe ser al menos :min caracteres.',
'array' => 'El :attribute debe tener como mínimo :min items.',
],
'no_double_extension' => 'El :attribute solo debe tener una extensión de archivo.',
'not_in' => 'El :attribute seleccionado es inválio.',
'not_regex' => 'El formato de :attribute es inválido.',
'numeric' => 'El :attribute debe ser numérico.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute debe ser al menos :min caracteres.',
'array' => ':attribute debe tener como mínimo :min items.',
],
'no_double_extension' => 'El :attribute debe tener una única extensión de archivo.',
'not_in' => ':attribute seleccionado es inválido.',
'not_regex' => 'El formato de :attribute es inválido.',
'numeric' => ':attribute debe ser numérico.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute doit contenir au moins :min caractères.',
'array' => ':attribute doit contenir au moins :min éléments.',
],
'no_double_extension' => ':attribute ne doit avoir qu\'une seule extension de fichier.',
'not_in' => 'L\'attribut sélectionné :attribute est invalide.',
'not_regex' => ':attribute a un format invalide.',
'numeric' => ':attribute doit être un nombre.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'שדה :attribute חייב להיות לפחות :min תווים.',
'array' => 'שדה :attribute חייב להיות לפחות :min פריטים.',
],
'no_double_extension' => 'השדה :attribute חייב להיות בעל סיומת קובץ אחת בלבד.',
'not_in' => 'בחירת ה-:attribute אינה תקפה.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'שדה :attribute חייב להיות מספר.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute legalább :min karakter kell legyen.',
'array' => ':attribute legalább :min elem kell legyen.',
],
'no_double_extension' => ':attribute csak egy fájlkiterjesztéssel rendelkezhet.',
'not_in' => 'A kiválasztott :attribute érvénytelen.',
'not_regex' => ':attribute formátuma érvénytelen.',
'numeric' => ':attribute szám kell legyen.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'Il campo :attribute deve essere almeno :min caratteri.',
'array' => 'Il campo :attribute deve contenere almeno :min elementi.',
],
'no_double_extension' => ':attribute deve avere solo un\'estensione.',
'not_in' => 'Il :attribute selezionato non è valido.',
'not_regex' => 'Il formato di :attribute non è valido.',
'numeric' => ':attribute deve essere un numero.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attributeは:min文字以上である必要があります。',
'array' => ':attributeは:min個以上である必要があります。',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => '選択された:attributeは不正です。',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => ':attributeは数値である必要があります。',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute(을)를 적어도 :value바이트로 구성하세요.',
'array' => ':attribute(을)를 적어도 :value개로 구성하세요..',
],
'no_double_extension' => ':attribute(이)가 단일한 확장자를 가져야 합니다.',
'not_in' => '고른 :attribute(이)가 유효하지 않습니다.',
'not_regex' => ':attribute(은)는 유효하지 않은 형식입니다.',
'numeric' => ':attribute(을)를 숫자로만 구성하세요.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute må være på minst :min tegn.',
'array' => ':attribute må minst ha :min ting.',
],
'no_double_extension' => ':attribute kan bare ha en formattype spesifisert.',
'not_in' => 'Den valgte :attribute er ugyldig.',
'not_regex' => ':attribute format er ugyldig.',
'numeric' => ':attribute må være et nummer.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute moet minstens :min karakters bevatten.',
'array' => ':attribute moet minstens :min items bevatten.',
],
'no_double_extension' => ':attribute mag maar een enkele bestandsextensie hebben.',
'not_in' => ':attribute is ongeldig.',
'not_regex' => ':attribute formaat is ongeldig.',
'numeric' => ':attribute moet een getal zijn.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'Długość :attribute nie może być mniejsza niż :min znaków.',
'array' => 'Rozmiar :attribute musi posiadać co najmniej :min elementy.',
],
'no_double_extension' => ':attribute może mieć tylko jedno rozszerzenie.',
'not_in' => 'Wartość :attribute jest nieprawidłowa.',
'not_regex' => 'Format :attribute jest nieprawidłowy.',
'numeric' => ':attribute musi być liczbą.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'O campo :attribute não deve ter menos que :min caracteres.',
'array' => 'O campo :attribute não deve ter menos que :min itens.',
],
'no_double_extension' => 'O campo :attribute deve ter apenas uma extensão de arquivo.',
'not_in' => 'O campo selecionado :attribute é inválido.',
'not_regex' => 'O formato do campo :attribute é inválido.',
'numeric' => 'O campo :attribute deve ser um número.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute должен быть минимум :min символов.',
'array' => ':attribute должен содержать хотя бы :min элементов.',
],
'no_double_extension' => ':attribute должен иметь только одно расширение файла.',
'not_in' => 'Выбранный :attribute некорректен.',
'not_regex' => 'Формат :attribute некорректен.',
'numeric' => ':attribute должен быть числом.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute musí mať aspoň :min znakov.',
'array' => ':attribute musí mať aspoň :min položiek.',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => 'Vybraný :attribute je neplatný.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => ':attribute musí byť číslo.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute mora biti najmanj :min znakov.',
'array' => ':attribute mora imeti vsaj :min elementov.',
],
'no_double_extension' => ':attribute mora imeti samo eno razširitveno datoteko',
'not_in' => 'Izbrani atribut je neveljaven.',
'not_regex' => ':attribute oblika ni veljavna.',
'numeric' => 'Atribut mora biti število.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute måste vara minst :min tecken.',
'array' => ':attribute måste ha minst :min poster.',
],
'no_double_extension' => ':attribute får bara ha ett filtillägg.',
'not_in' => 'Vald :attribute är inte giltig',
'not_regex' => 'Formatet på :attribute är ogiltigt.',
'numeric' => ':attribute måste vara ett nummer.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'no_double_extension' => 'The :attribute must only have a single file extension.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute, en az :min karakter içermelidir.',
'array' => ':attribute, en az :min öge içermelidir.',
],
'no_double_extension' => ':attribute, sadece tek bir dosya tipinde olmalıdır.',
'not_in' => 'Seçili :attribute geçersiz.',
'not_regex' => ':attribute formatı geçersiz.',
'numeric' => ':attribute, bir sayı olmalıdır.',

View File

@ -78,7 +78,6 @@ return [
'string' => 'Текст в полі :attribute повинен містити не менше :min символів.',
'array' => 'Поле :attribute повинне містити не менше :min елементів.',
],
'no_double_extension' => 'Поле :attribute повинне містити тільки одне розширення файлу.',
'not_in' => 'Вибране для :attribute значення не коректне.',
'not_regex' => 'Формат поля :attribute не вірний.',
'numeric' => 'Поле :attribute повинно містити число.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute phải có tối thiểu :min ký tự.',
'array' => ':attribute phải có tối thiểu :min mục.',
],
'no_double_extension' => ':attribute chỉ được có một định dạng mở rộng duy nhất.',
'not_in' => ':attribute đã chọn không hợp lệ.',
'not_regex' => 'Định dạng của :attribute không hợp lệ.',
'numeric' => ':attribute phải là một số.',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute 至少为:min个字符。',
'array' => ':attribute 至少有:min项。',
],
'no_double_extension' => ':attribute 必须具有一个扩展名。',
'not_in' => '选中的 :attribute 无效。',
'not_regex' => ':attribute 格式错误。',
'numeric' => ':attribute 必须是一个数。',

View File

@ -78,7 +78,6 @@ return [
'string' => ':attribute 至少為:min個字元。',
'array' => ':attribute 至少有:min項。',
],
'no_double_extension' => 'The :attribute必須僅具有一個文件擴展名。',
'not_in' => '選中的 :attribute 無效。',
'not_regex' => 'The :attribute格式無效。',
'numeric' => ':attribute 必須是一個數。',

View File

@ -165,7 +165,7 @@ class ImageTest extends TestCase
$this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
}
public function test_files_with_double_extensions_cannot_be_uploaded()
public function test_files_with_double_extensions_will_get_sanitized()
{
$page = Page::first();
$admin = $this->getAdmin();
@ -177,9 +177,17 @@ class ImageTest extends TestCase
$file = $this->newTestImageFromBase64('bad-phtml-png.base64', $fileName);
$upload = $this->withHeader('Content-Type', 'image/png')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
$upload->assertStatus(302);
$upload->assertStatus(200);
$this->assertFalse(file_exists(public_path($relPath)), 'Uploaded double extension file was uploaded but should have been stopped');
$lastImage = Image::query()->latest('id')->first();
$newFileName = explode('.', basename($lastImage->path))[0];
$this->assertEquals($lastImage->name, 'bad-phtml.png');
$this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image file name was not stripped of dots');
$this->assertTrue(strlen($newFileName) > 0, 'File name was reduced to nothing');
$this->deleteImage($lastImage->path);
}
public function test_url_entities_removed_from_filenames()