1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Translation\Loader; 13 14use Symfony\Component\Translation\Exception\InvalidResourceException; 15 16/** 17 * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) 18 */ 19class MoFileLoader extends FileLoader 20{ 21 /** 22 * Magic used for validating the format of an MO file as well as 23 * detecting if the machine used to create that file was little endian. 24 */ 25 public const MO_LITTLE_ENDIAN_MAGIC = 0x950412de; 26 27 /** 28 * Magic used for validating the format of an MO file as well as 29 * detecting if the machine used to create that file was big endian. 30 */ 31 public const MO_BIG_ENDIAN_MAGIC = 0xde120495; 32 33 /** 34 * The size of the header of an MO file in bytes. 35 */ 36 public const MO_HEADER_SIZE = 28; 37 38 /** 39 * Parses machine object (MO) format, independent of the machine's endian it 40 * was created on. Both 32bit and 64bit systems are supported. 41 * 42 * {@inheritdoc} 43 */ 44 protected function loadResource($resource) 45 { 46 $stream = fopen($resource, 'r'); 47 48 $stat = fstat($stream); 49 50 if ($stat['size'] < self::MO_HEADER_SIZE) { 51 throw new InvalidResourceException('MO stream content has an invalid format.'); 52 } 53 $magic = unpack('V1', fread($stream, 4)); 54 $magic = hexdec(substr(dechex(current($magic)), -8)); 55 56 if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) { 57 $isBigEndian = false; 58 } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) { 59 $isBigEndian = true; 60 } else { 61 throw new InvalidResourceException('MO stream content has an invalid format.'); 62 } 63 64 // formatRevision 65 $this->readLong($stream, $isBigEndian); 66 $count = $this->readLong($stream, $isBigEndian); 67 $offsetId = $this->readLong($stream, $isBigEndian); 68 $offsetTranslated = $this->readLong($stream, $isBigEndian); 69 // sizeHashes 70 $this->readLong($stream, $isBigEndian); 71 // offsetHashes 72 $this->readLong($stream, $isBigEndian); 73 74 $messages = []; 75 76 for ($i = 0; $i < $count; ++$i) { 77 $pluralId = null; 78 $translated = null; 79 80 fseek($stream, $offsetId + $i * 8); 81 82 $length = $this->readLong($stream, $isBigEndian); 83 $offset = $this->readLong($stream, $isBigEndian); 84 85 if ($length < 1) { 86 continue; 87 } 88 89 fseek($stream, $offset); 90 $singularId = fread($stream, $length); 91 92 if (false !== strpos($singularId, "\000")) { 93 [$singularId, $pluralId] = explode("\000", $singularId); 94 } 95 96 fseek($stream, $offsetTranslated + $i * 8); 97 $length = $this->readLong($stream, $isBigEndian); 98 $offset = $this->readLong($stream, $isBigEndian); 99 100 if ($length < 1) { 101 continue; 102 } 103 104 fseek($stream, $offset); 105 $translated = fread($stream, $length); 106 107 if (false !== strpos($translated, "\000")) { 108 $translated = explode("\000", $translated); 109 } 110 111 $ids = ['singular' => $singularId, 'plural' => $pluralId]; 112 $item = compact('ids', 'translated'); 113 114 if (!empty($item['ids']['singular'])) { 115 $id = $item['ids']['singular']; 116 if (isset($item['ids']['plural'])) { 117 $id .= '|'.$item['ids']['plural']; 118 } 119 $messages[$id] = stripcslashes(implode('|', (array) $item['translated'])); 120 } 121 } 122 123 fclose($stream); 124 125 return array_filter($messages); 126 } 127 128 /** 129 * Reads an unsigned long from stream respecting endianness. 130 * 131 * @param resource $stream 132 */ 133 private function readLong($stream, bool $isBigEndian): int 134 { 135 $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); 136 $result = current($result); 137 138 return (int) substr($result, -8); 139 } 140} 141