Skip to content

Commit

Permalink
feat: add sniffing functionality (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
neoncitylights committed Apr 4, 2024
1 parent 8dd38e8 commit 9815346
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/Sniff/SniffAgent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Neoncitylights\MediaType\Sniff;

use Neoncitylights\MediaType\MediaType;

final class SniffAgent {
/**
* @see https://mimesniff.spec.whatwg.org/#supplied-mime-type
*/
public readonly MediaType $suppliedMediaType;

/**
* @see https://mimesniff.spec.whatwg.org/#check-for-apache-bug-flag
*/
public readonly bool $checkForApacheFlag;

/**
* @see https://mimesniff.spec.whatwg.org/#no-sniff-flag
*/
public readonly bool $noSniffFlag;

/**
* https://mimesniff.spec.whatwg.org/#computed-mime-type
*/
public MediaType|null $computedMimeType;

public function __construct(
MediaType $suppliedMediaType,
bool $checkForApacheFlag,
bool $noSniffFlag
) {
$this->suppliedMediaType = $suppliedMediaType;
$this->computedMimeType = null;
$this->checkForApacheFlag = $checkForApacheFlag;
$this->noSniffFlag = $noSniffFlag;
}
}
64 changes: 64 additions & 0 deletions src/Sniff/SniffTableLookup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Neoncitylights\MediaType\Sniff;

use Neoncitylights\MediaType\MediaType;

final class SniffTableLookup {
public const IMAGE = [
[
[ 0x00, 0x00, 0x01, 0x00 ],
[ 0xFF, 0xFF, 0xFF, 0xFF ],
[ ],
[ 'image', 'x-icon' ],
],
[
[ 0x00, 0x00, 0x02, 0x00 ],
[ 0xFF, 0xFF, 0xFF, 0xFF ],
[ ],
[ 'image', 'x-icon' ],
],
[
[ 0x42, 0x4D ],
[ 0xFF, 0xFF ],
[ ],
[ 'image', 'bmp' ],
],
[
[ 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 ],
[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ],
[ ],
[ 'image', 'gif' ],
],
[
[ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 ],
[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ],
[ ],
[ 'image', 'gif' ],
],
[
[
0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00,
0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50
],
[
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
],
[ ],
[ 'image', 'png' ],
],
[
[ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ],
[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ],
[ ],
[ 'image', 'png' ],
],
[
[ 0xFF, 0xD8, 0xFF ],
[ 0xFF, 0xFF, 0xFF ],
[ ],
[ 'image', 'jpeg' ],
],
];
}
39 changes: 39 additions & 0 deletions src/Sniff/SniffTableValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Neoncitylights\MediaType\Sniff;

use Neoncitylights\MediaType\MediaType;

final class SniffTableValue {
/**
* @see https://mimesniff.spec.whatwg.org/#byte-pattern
* @var int[]
*/
public readonly array $pattern;

/**
* @see https://mimesniff.spec.whatwg.org/#pattern-mask
* @var int[]
*/
public readonly array $patternMask;

/**
* @see https://mimesniff.spec.whatwg.org/#bytes-ignored
* @var int[]
*/
public readonly array $bytesIgnored;

public readonly MediaType $mediaType;

public function __construct(
array $pattern,
array $patternMask,
array $bytesIgnored,
MediaType $mediaType
) {
$this->pattern = $pattern;
$this->patternMask = $patternMask;
$this->bytesIgnored = $bytesIgnored;
$this->mediaType = $mediaType;
}
}
117 changes: 117 additions & 0 deletions src/Sniff/Sniffer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace Neoncitylights\MediaType\Sniff;

use Neoncitylights\MediaType\MediaType;

final class Sniffer {
/**
* Undocumented function
*
* @param array $tableLookup
* @param array $sequence
* @return void
*/
public function performTableLookup( array $tableLookup, array $sequence ) {
$imageTable = SniffTableLookup::IMAGE;
foreach( $imageTable as $tableRecord ) {
$lookupValue = new SniffTableValue(
$tableRecord[0],
$tableRecord[1],
$tableRecord[2],
new MediaType( $tableRecord[3][0], $tableRecord[3][1], [] )
);

$patternMatched = $this->doesByteSequenceMatchPattern(
$sequence,
$lookupValue->pattern,
$lookupValue->patternMask,
$lookupValue->bytesIgnored
);

if ( $patternMatched ) {
return $lookupValue->mediaType;
}
}
}

private function matchesSignatureForMP4( array $input ) {
$length = count( $input );
if ( $length < 12 ) {
return false;
}

$boxSize = \intval(\pack( "L*", ...\array_slice( $input, 0, 4 ) ));
if ( $length < $boxSize || $boxSize % 4 !== 0 ) {
return false;
}

$bytes4to7 = \array_slice( $input, 4, 4 );
if( $bytes4to7 !== [ 0x66, 0x74, 0x79, 0x70 ] ) {
return false;
}

$bytes8to10 = \array_slice( $input, 8, 3 );
$mp4Signature = [ 0x6D, 0x70, 0x34 ];
if ( $bytes8to10 === $mp4Signature ) {
return true;
}

$bytesRead = 16;
while ( $bytesRead < $boxSize ) {
$byteSlice = [ $input[$bytesRead], $input[$bytesRead + 1], $input[$bytesRead + 2] ];
if ( $byteSlice === $mp4Signature ) {
return true;
}
$bytesRead += 4;
}

return false;
}

private function matchesSignatureForWebM() {

}

/**
* @see https://mimesniff.spec.whatwg.org/#pattern-matching-algorithm
* @param int[] $input
* @param int[] $pattern
* @param int[] $patternMask
* @param int[] $bytesIgnored
* @return boolean
*/
private function doesByteSequenceMatchPattern(
array $input,
array $pattern,
array $patternMask,
array $bytesIgnored
): bool {
$inputLen = count( $input );
$patternLen = count( $pattern );

if ( $inputLen < $patternLen ) {
return false;
}

$s = 0;
while( $s < $inputLen ) {
if ( !in_array( $input[$s], $bytesIgnored ) ) {
return false;
}
$s++;
}

$p = 0;
while ( $p < $patternLen ) {
$maskedData = $input[$s] & $patternMask[$p];
if ( $maskedData !== $pattern[$p] ) {
return false;
}
$s++;
$p++;
}

return true;
}
}

0 comments on commit 9815346

Please sign in to comment.