SniffFirstFourBytes::doDetect()   D
last analyzed

Complexity

Conditions 22
Paths 104

Size

Total Lines 104
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 30.3216

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 22
eloc 63
c 1
b 0
f 0
nc 104
nop 1
dl 0
loc 104
ccs 46
cts 62
cp 0.7419
crap 30.3216
rs 4.1333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace ImageMimeTypeGuesser\Detectors;
4
5
use \ImageMimeTypeGuesser\Detectors\AbstractDetector;
6
7
class SniffFirstFourBytes extends AbstractDetector
8
{
9
10
    /**
11
     * Try to detect mime type by sniffing the first four bytes.
12
     *
13
     * Returns:
14
     * - mime type (string) (if it is in fact an image, and type could be determined)
15
     * - false (if it is not an image type that the server knowns about)
16
     * - null  (if nothing can be determined)
17
     *
18
     * @param  string  $filePath  The path to the file
19
     * @return string|false|null  mimetype (if it is an image, and type could be determined),
20
     *    false (if it is not an image type that the server knowns about)
21
     *    or null (if nothing can be determined)
22
     */
23 7
    protected function doDetect($filePath)
24
    {
25 7
        $handle = @fopen($filePath, 'r');
26 7
        if ($handle === false) {
27
            return null;
28
        }
29
        // 20 bytes is sufficient for all our sniffers, except image/svg+xml.
30
        // The svg sniffer takes care of reading more
31 7
        $sampleBin = @fread($handle, 20);
32 7
        if ($sampleBin === false) {
33
            return null;
34
        }
35 7
        $firstByte = $sampleBin[0];
36 7
        $sampleHex = strtoupper(bin2hex($sampleBin));
37
38 7
        $hexPatterns = [];
39 7
        $binPatterns = [];
40
41
        // https://www.rapidtables.com/convert/number/hex-to-ascii.html
42
        switch ($firstByte) {
43 7
            case "\x00":
44 2
                $hexPatterns[] = ['image/x-icon', "/^00000(1?2)00/"];
45
46 2
                if (preg_match("/^.{8}6A502020/", $sampleHex) === 1) {
47
                    // jpeg-2000 - a bit more complex, as block size may vary
48
                    // https://www.file-recovery.com/jp2-signature-format.htm
49 2
                    $block1Size = hexdec("0x" . substr($sampleHex, 0, 8));
50
51 2
                    $moreBytes = @fread($handle, $block1Size + 4 + 8);
0 ignored issues
show
Bug introduced by
$block1Size + 4 + 8 of type double is incompatible with the type integer expected by parameter $length of fread(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

51
                    $moreBytes = @fread($handle, /** @scrutinizer ignore-type */ $block1Size + 4 + 8);
Loading history...
52 2
                    if ($moreBytes !== false) {
53 2
                        $sampleBin .= $moreBytes;
54
                    }
55 2
                    if (substr($sampleBin, $block1Size + 4, 4) == 'ftyp') {
0 ignored issues
show
Bug introduced by
$block1Size + 4 of type double is incompatible with the type integer expected by parameter $offset of substr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

55
                    if (substr($sampleBin, /** @scrutinizer ignore-type */ $block1Size + 4, 4) == 'ftyp') {
Loading history...
56 2
                        $subtyp = substr($sampleBin, $block1Size + 8, 4);
57 2
                        if ($subtyp == 'mjp2') {
58
                            return 'video/mj2';
59
                        } else {
60 2
                            return 'image/' . rtrim($subtyp);
61
                        }
62
                    }
63
                }
64
65
                break;
66
67 6
            case "8":
68
                $binPatterns[] = ['application/psd', "/^8BPS/"];
69
                break;
70
71 6
            case "B":
72
                $binPatterns[] = ['image/bmp', "/^BM/"];
73
                break;
74
75 6
            case "G":
76 2
                $binPatterns[] = ['image/gif', "/^GIF8(7|9)a/"];
77 2
                break;
78
79 5
            case "I":
80 2
                $hexPatterns[] = ['image/tiff', "/^(49492A00|4D4D002A)/"];
81 2
                break;
82
83 4
            case "R":
84
                // PS: Another library is more specific: /^RIFF.{4}WEBPVP/
85
                // Is "VP" always there?
86 1
                $binPatterns[] = ['image/webp', "/^RIFF.{4}WEBP/"];
87 1
                break;
88
89 3
            case "<":
90
                // Another library looks for end bracket for svg.
91
                // We do not, as it requires more bytes read.
92
                // Note that <xml> tag might be big too... - so we read in 200 extra
93
                $moreBytes = @fread($handle, 200);
94
                if ($moreBytes !== false) {
95
                    $sampleBin .= $moreBytes;
96
                }
97
                $binPatterns[] = ['image/svg+xml', "/^(<\?xml[^>]*\?>.*)?<svg/is"];
98
                break;
99
100 3
            case "f":
101
                //$hexPatterns[] = ['image/heic', "/667479706865(6963|6978|7663|696D|6973|766D|7673)/"];
102
                //$hexPatterns[] = ['image/heif', "/667479706D(69|73)6631)/"];
103
                $binPatterns[] = ['image/heic', "/ftyphe(ic|ix|vc|im|is|vm|vs)/"];
104
                $binPatterns[] = ['image/heif', "/ftypm(i|s)f1/"];
105
                break;
106
107 3
            case "\x89":
108 2
                $hexPatterns[] = ['image/png', "/^89504E470D0A1A0A/"];
109 2
                break;
110
111 2
            case "\xFF":
112 2
                $hexPatterns[] = ['image/jpeg', "/^FFD8FF(DB|E0|EE|E1)/"];
113 2
                break;
114
        }
115
116 6
        foreach ($hexPatterns as list($mime, $pattern)) {
117 4
            if (preg_match($pattern, $sampleHex) === 1) {
118 4
                return $mime;
119
            }
120
        }
121 3
        foreach ($binPatterns as list($mime, $pattern)) {
122 3
            if (preg_match($pattern, $sampleBin) === 1) {
123 3
                return $mime;
124
            }
125
        }
126 1
        return null;
127
128
        /*
129
        https://en.wikipedia.org/wiki/List_of_file_signatures
130
        https://github.com/zjsxwc/mime-type-sniffer/blob/master/src/MimeTypeSniffer/MimeTypeSniffer.php
131
        http://phil.lavin.me.uk/2011/12/php-accurately-detecting-the-type-of-a-file/
132
133
*/
134
        // TODO: JPEG 2000
135
        // mime types: image/jp2, image/jpf, image/jpx, image/jpm
136
        // http://fileformats.archiveteam.org/wiki/JPEG_2000
137
        // https://www.file-recovery.com/jp2-signature-format.htm
138
        /*
139
        From: https://github.com/Tinram/File-Identifier/blob/master/src/FileSignatures.php
140
        'JPG 2000' => '00 00 00 0c 6a 50 20 20 0d 0a 87 0a',
141
        https://filesignatures.net/index.php?page=search&search=JP2&mode=EXT
142
        */
143
    }
144
}
145