first
This commit is contained in:
77
app/Rules/ValidAudioMagicBytes.php
Normal file
77
app/Rules/ValidAudioMagicBytes.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class ValidAudioMagicBytes implements ValidationRule
|
||||
{
|
||||
// Signatures checked at byte offset 0
|
||||
private const SIGNATURES = [
|
||||
[0xFF, 0xFB], // MP3 MPEG-1 Layer 3
|
||||
[0xFF, 0xF3], // MP3 MPEG-2 Layer 3
|
||||
[0xFF, 0xF2], // MP3 MPEG-2.5 Layer 3
|
||||
[0xFF, 0xF1], // AAC ADTS MPEG-4
|
||||
[0xFF, 0xF9], // AAC ADTS MPEG-2
|
||||
[0x49, 0x44, 0x33], // MP3 with ID3v2 tag
|
||||
[0x52, 0x49, 0x46, 0x46], // WAV (RIFF header)
|
||||
[0x4F, 0x67, 0x67, 0x53], // OGG (OggS)
|
||||
[0x66, 0x4C, 0x61, 0x43], // FLAC (fLaC)
|
||||
[0x1A, 0x45, 0xDF, 0xA3], // WebM / MKV
|
||||
];
|
||||
|
||||
// MP4/M4A: ISO Base Media file — 'ftyp' box starts at byte offset 4
|
||||
private const MP4_SIGNATURE = [0x66, 0x74, 0x79, 0x70]; // 'ftyp'
|
||||
private const MP4_OFFSET = 4;
|
||||
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if (! ($value instanceof UploadedFile) || ! $value->isValid()) {
|
||||
$fail('Fail audio tidak sah.');
|
||||
return;
|
||||
}
|
||||
|
||||
$handle = @fopen($value->getRealPath(), 'rb');
|
||||
if ($handle === false) {
|
||||
$fail('Fail audio tidak dapat dibaca.');
|
||||
return;
|
||||
}
|
||||
|
||||
$header = fread($handle, 12);
|
||||
fclose($handle);
|
||||
|
||||
if (strlen($header) < 4) {
|
||||
$fail('Fail audio terlalu kecil atau rosak.');
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes = array_values(unpack('C*', $header));
|
||||
|
||||
foreach (self::SIGNATURES as $sig) {
|
||||
if ($this->matchesAt($bytes, $sig, 0)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check MP4/M4A: need at least 8 bytes
|
||||
if (count($bytes) >= self::MP4_OFFSET + count(self::MP4_SIGNATURE)) {
|
||||
if ($this->matchesAt($bytes, self::MP4_SIGNATURE, self::MP4_OFFSET)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$fail('Format fail audio tidak sah. Kandungan fail tidak menepati format audio yang dibenarkan.');
|
||||
}
|
||||
|
||||
private function matchesAt(array $bytes, array $signature, int $offset): bool
|
||||
{
|
||||
foreach ($signature as $i => $expected) {
|
||||
if (($bytes[$offset + $i] ?? -1) !== $expected) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user