| <?php |
| |
| /* |
| * This file is part of the Symfony package. |
| * |
| * (c) Fabien Potencier <fabien@symfony.com> |
| * |
| * For the full copyright and license information, please view the LICENSE |
| * file that was distributed with this source code. |
| */ |
| |
| namespace Symfony\Component\Translation\Loader; |
| |
| use Symfony\Component\Translation\Exception\InvalidResourceException; |
| |
| /** |
| * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) |
| */ |
| class MoFileLoader extends FileLoader |
| { |
| /** |
| * Magic used for validating the format of an MO file as well as |
| * detecting if the machine used to create that file was little endian. |
| */ |
| public const MO_LITTLE_ENDIAN_MAGIC = 0x950412de; |
| |
| /** |
| * Magic used for validating the format of an MO file as well as |
| * detecting if the machine used to create that file was big endian. |
| */ |
| public const MO_BIG_ENDIAN_MAGIC = 0xde120495; |
| |
| /** |
| * The size of the header of an MO file in bytes. |
| */ |
| public const MO_HEADER_SIZE = 28; |
| |
| /** |
| * Parses machine object (MO) format, independent of the machine's endian it |
| * was created on. Both 32bit and 64bit systems are supported. |
| * |
| * {@inheritdoc} |
| */ |
| protected function loadResource(string $resource) |
| { |
| $stream = fopen($resource, 'r'); |
| |
| $stat = fstat($stream); |
| |
| if ($stat['size'] < self::MO_HEADER_SIZE) { |
| throw new InvalidResourceException('MO stream content has an invalid format.'); |
| } |
| $magic = unpack('V1', fread($stream, 4)); |
| $magic = hexdec(substr(dechex(current($magic)), -8)); |
| |
| if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) { |
| $isBigEndian = false; |
| } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) { |
| $isBigEndian = true; |
| } else { |
| throw new InvalidResourceException('MO stream content has an invalid format.'); |
| } |
| |
| // formatRevision |
| $this->readLong($stream, $isBigEndian); |
| $count = $this->readLong($stream, $isBigEndian); |
| $offsetId = $this->readLong($stream, $isBigEndian); |
| $offsetTranslated = $this->readLong($stream, $isBigEndian); |
| // sizeHashes |
| $this->readLong($stream, $isBigEndian); |
| // offsetHashes |
| $this->readLong($stream, $isBigEndian); |
| |
| $messages = []; |
| |
| for ($i = 0; $i < $count; ++$i) { |
| $pluralId = null; |
| $translated = null; |
| |
| fseek($stream, $offsetId + $i * 8); |
| |
| $length = $this->readLong($stream, $isBigEndian); |
| $offset = $this->readLong($stream, $isBigEndian); |
| |
| if ($length < 1) { |
| continue; |
| } |
| |
| fseek($stream, $offset); |
| $singularId = fread($stream, $length); |
| |
| if (str_contains($singularId, "\000")) { |
| [$singularId, $pluralId] = explode("\000", $singularId); |
| } |
| |
| fseek($stream, $offsetTranslated + $i * 8); |
| $length = $this->readLong($stream, $isBigEndian); |
| $offset = $this->readLong($stream, $isBigEndian); |
| |
| if ($length < 1) { |
| continue; |
| } |
| |
| fseek($stream, $offset); |
| $translated = fread($stream, $length); |
| |
| if (str_contains($translated, "\000")) { |
| $translated = explode("\000", $translated); |
| } |
| |
| $ids = ['singular' => $singularId, 'plural' => $pluralId]; |
| $item = compact('ids', 'translated'); |
| |
| if (!empty($item['ids']['singular'])) { |
| $id = $item['ids']['singular']; |
| if (isset($item['ids']['plural'])) { |
| $id .= '|'.$item['ids']['plural']; |
| } |
| $messages[$id] = stripcslashes(implode('|', (array) $item['translated'])); |
| } |
| } |
| |
| fclose($stream); |
| |
| return array_filter($messages); |
| } |
| |
| /** |
| * Reads an unsigned long from stream respecting endianness. |
| * |
| * @param resource $stream |
| */ |
| private function readLong($stream, bool $isBigEndian): int |
| { |
| $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); |
| $result = current($result); |
| |
| return (int) substr($result, -8); |
| } |
| } |