3 namespace BookStack\Uploads;
5 use BookStack\Exceptions\DrawioPngReaderException;
8 * Reads the PNG file format: https://www.w3.org/TR/2003/REC-PNG-20031110/
9 * So that it can extract embedded drawing data for alternative use.
14 * @param resource $fileStream
16 public function __construct(
22 * @throws DrawioPngReaderException
24 public function extractDrawing(): string
26 $signature = fread($this->fileStream, 8);
27 $pngSignature = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
28 if ($signature !== $pngSignature) {
29 throw new DrawioPngReaderException('File does not appear to be a valid PNG file');
36 fseek($this->fileStream, $offset);
38 $lengthBytes = $this->readData(4);
39 $chunkTypeBytes = $this->readData(4);
40 $length = unpack('Nvalue', $lengthBytes)['value'];
42 if ($chunkTypeBytes === 'tEXt') {
43 fseek($this->fileStream, $offset + 8);
44 $data = $this->readData($length);
45 $crc = $this->readData(4);
46 $drawingData = $this->readTextForDrawing($data);
47 if ($drawingData !== null) {
48 $crcResult = $this->calculateCrc($chunkTypeBytes . $data);
49 if ($crc !== $crcResult) {
50 throw new DrawioPngReaderException('Drawing data withing PNG file appears to be corrupted');
54 } else if ($chunkTypeBytes === 'IEND') {
58 $offset += 12 + $length; // 12 = length + type + crc bytes
61 throw new DrawioPngReaderException('Unable to find drawing data within PNG file');
64 protected function readTextForDrawing(string $data): ?string
66 // Check the keyword is mxfile to ensure we're getting the right data
67 if (!str_starts_with($data, "mxfile\u{0}")) {
71 // Extract & cleanup the drawing text
72 $drawingText = substr($data, 7);
73 return urldecode($drawingText);
76 protected function readData(int $length): string
78 $bytes = fread($this->fileStream, $length);
79 if ($bytes === false || strlen($bytes) < $length) {
80 throw new DrawioPngReaderException('Unable to find drawing data within PNG file');
85 protected function getCrcTable(): array
89 for ($n = 0; $n < 256; $n++) {
91 for ($k = 0; $k < 8; $k++) {
93 $c = 0xedb88320 ^ ($c >> 1);
105 * Calculate a CRC for the given bytes following:
106 * https://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix
108 protected function calculateCrc(string $bytes): string
110 $table = $this->getCrcTable();
112 $length = strlen($bytes);
115 for ($n = 0; $n < $length; $n++) {
116 $tableIndex = ($c ^ ord($bytes[$n])) & 0xff;
117 $c = $table[$tableIndex] ^ ($c >> 8);
120 return pack('N', $c ^ 0xffffffff);