1 | <?php |
||||
2 | /** |
||||
3 | * @link https://www.yiiframework.com/ |
||||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||||
5 | * @license https://www.yiiframework.com/license/ |
||||
6 | */ |
||||
7 | |||||
8 | namespace yii\i18n; |
||||
9 | |||||
10 | use yii\base\Exception; |
||||
11 | |||||
12 | /** |
||||
13 | * GettextMoFile represents an MO Gettext message file. |
||||
14 | * |
||||
15 | * This class is written by adapting Michael's Gettext_MO class in PEAR. |
||||
16 | * Please refer to the following license terms. |
||||
17 | * |
||||
18 | * Copyright (c) 2004-2005, Michael Wallner <[email protected]>. |
||||
19 | * All rights reserved. |
||||
20 | * |
||||
21 | * Redistribution and use in source and binary forms, with or without |
||||
22 | * modification, are permitted provided that the following conditions are met: |
||||
23 | * |
||||
24 | * * Redistributions of source code must retain the above copyright notice, |
||||
25 | * this list of conditions and the following disclaimer. |
||||
26 | * * Redistributions in binary form must reproduce the above copyright |
||||
27 | * notice, this list of conditions and the following disclaimer in the |
||||
28 | * documentation and/or other materials provided with the distribution. |
||||
29 | * |
||||
30 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
31 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
32 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
33 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
||||
34 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||
35 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||
36 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
37 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
||||
38 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
39 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
40 | * |
||||
41 | * @author Qiang Xue <[email protected]> |
||||
42 | * @since 2.0 |
||||
43 | */ |
||||
44 | class GettextMoFile extends GettextFile |
||||
45 | { |
||||
46 | /** |
||||
47 | * @var bool whether to use big-endian when reading and writing an integer. |
||||
48 | */ |
||||
49 | public $useBigEndian = false; |
||||
50 | |||||
51 | |||||
52 | /** |
||||
53 | * Loads messages from an MO file. |
||||
54 | * @param string $filePath file path |
||||
55 | * @param string $context message context |
||||
56 | * @return array message translations. Array keys are source messages and array values are translated messages: |
||||
57 | * source message => translated message. |
||||
58 | * @throws Exception if unable to read the MO file |
||||
59 | */ |
||||
60 | 2 | public function load($filePath, $context) |
|||
61 | { |
||||
62 | 2 | if (false === ($fileHandle = @fopen($filePath, 'rb'))) { |
|||
63 | throw new Exception('Unable to read file "' . $filePath . '".'); |
||||
64 | } |
||||
65 | 2 | if (false === @flock($fileHandle, LOCK_SH)) { |
|||
66 | throw new Exception('Unable to lock file "' . $filePath . '" for reading.'); |
||||
67 | } |
||||
68 | |||||
69 | // magic |
||||
70 | 2 | $array = unpack('c', $this->readBytes($fileHandle, 4)); |
|||
71 | 2 | $magic = current($array); |
|||
72 | 2 | if ($magic == -34) { |
|||
73 | 2 | $this->useBigEndian = false; |
|||
74 | } elseif ($magic == -107) { |
||||
75 | $this->useBigEndian = true; |
||||
76 | } else { |
||||
77 | throw new Exception('Invalid MO file: ' . $filePath . ' (magic: ' . $magic . ').'); |
||||
78 | } |
||||
79 | |||||
80 | // revision |
||||
81 | 2 | $revision = $this->readInteger($fileHandle); |
|||
82 | 2 | if ($revision !== 0) { |
|||
83 | throw new Exception('Invalid MO file revision: ' . $revision . '.'); |
||||
84 | } |
||||
85 | |||||
86 | 2 | $count = $this->readInteger($fileHandle); |
|||
87 | 2 | $sourceOffset = $this->readInteger($fileHandle); |
|||
88 | 2 | $targetOffset = $this->readInteger($fileHandle); |
|||
89 | |||||
90 | 2 | $sourceLengths = []; |
|||
91 | 2 | $sourceOffsets = []; |
|||
92 | 2 | fseek($fileHandle, $sourceOffset); |
|||
93 | 2 | for ($i = 0; $i < $count; ++$i) { |
|||
94 | 2 | $sourceLengths[] = $this->readInteger($fileHandle); |
|||
95 | 2 | $sourceOffsets[] = $this->readInteger($fileHandle); |
|||
96 | } |
||||
97 | |||||
98 | 2 | $targetLengths = []; |
|||
99 | 2 | $targetOffsets = []; |
|||
100 | 2 | fseek($fileHandle, $targetOffset); |
|||
101 | 2 | for ($i = 0; $i < $count; ++$i) { |
|||
102 | 2 | $targetLengths[] = $this->readInteger($fileHandle); |
|||
103 | 2 | $targetOffsets[] = $this->readInteger($fileHandle); |
|||
104 | } |
||||
105 | |||||
106 | 2 | $messages = []; |
|||
107 | 2 | for ($i = 0; $i < $count; ++$i) { |
|||
108 | 2 | $id = $this->readString($fileHandle, $sourceLengths[$i], $sourceOffsets[$i]); |
|||
109 | 2 | $separatorPosition = strpos((string)$id, chr(4)); |
|||
110 | |||||
111 | |||||
112 | 2 | if ((!$context && $separatorPosition === false) || ($context && $separatorPosition !== false && strncmp($id, $context, $separatorPosition) === 0)) { |
|||
113 | 2 | if ($separatorPosition !== false) { |
|||
114 | 2 | $id = substr($id, $separatorPosition + 1); |
|||
115 | } |
||||
116 | |||||
117 | 2 | $message = $this->readString($fileHandle, $targetLengths[$i], $targetOffsets[$i]); |
|||
118 | 2 | $messages[$id] = $message; |
|||
119 | } |
||||
120 | } |
||||
121 | |||||
122 | 2 | @flock($fileHandle, LOCK_UN); |
|||
0 ignored issues
–
show
|
|||||
123 | 2 | @fclose($fileHandle); |
|||
0 ignored issues
–
show
It seems like you do not handle an error condition for
fclose() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
124 | |||||
125 | 2 | return $messages; |
|||
126 | } |
||||
127 | |||||
128 | /** |
||||
129 | * Saves messages to an MO file. |
||||
130 | * @param string $filePath file path |
||||
131 | * @param array $messages message translations. Array keys are source messages and array values are |
||||
132 | * translated messages: source message => translated message. Note if the message has a context, |
||||
133 | * the message ID must be prefixed with the context with chr(4) as the separator. |
||||
134 | * @throws Exception if unable to save the MO file |
||||
135 | */ |
||||
136 | 1 | public function save($filePath, $messages) |
|||
137 | { |
||||
138 | 1 | if (false === ($fileHandle = @fopen($filePath, 'wb'))) { |
|||
139 | throw new Exception('Unable to write file "' . $filePath . '".'); |
||||
140 | } |
||||
141 | 1 | if (false === @flock($fileHandle, LOCK_EX)) { |
|||
142 | throw new Exception('Unable to lock file "' . $filePath . '" for reading.'); |
||||
143 | } |
||||
144 | |||||
145 | // magic |
||||
146 | 1 | if ($this->useBigEndian) { |
|||
147 | $this->writeBytes($fileHandle, pack('c*', 0x95, 0x04, 0x12, 0xde)); // -107 |
||||
148 | } else { |
||||
149 | 1 | $this->writeBytes($fileHandle, pack('c*', 0xde, 0x12, 0x04, 0x95)); // -34 |
|||
150 | } |
||||
151 | |||||
152 | // revision |
||||
153 | 1 | $this->writeInteger($fileHandle, 0); |
|||
154 | |||||
155 | // message count |
||||
156 | 1 | $messageCount = count($messages); |
|||
157 | 1 | $this->writeInteger($fileHandle, $messageCount); |
|||
158 | |||||
159 | // offset of source message table |
||||
160 | 1 | $offset = 28; |
|||
161 | 1 | $this->writeInteger($fileHandle, $offset); |
|||
162 | 1 | $offset += $messageCount * 8; |
|||
163 | 1 | $this->writeInteger($fileHandle, $offset); |
|||
164 | |||||
165 | // hashtable size, omitted |
||||
166 | 1 | $this->writeInteger($fileHandle, 0); |
|||
167 | 1 | $offset += $messageCount * 8; |
|||
168 | 1 | $this->writeInteger($fileHandle, $offset); |
|||
169 | |||||
170 | // length and offsets for source messages |
||||
171 | 1 | foreach (array_keys($messages) as $id) { |
|||
172 | 1 | $length = strlen($id); |
|||
173 | 1 | $this->writeInteger($fileHandle, $length); |
|||
174 | 1 | $this->writeInteger($fileHandle, $offset); |
|||
175 | 1 | $offset += $length + 1; |
|||
176 | } |
||||
177 | |||||
178 | // length and offsets for target messages |
||||
179 | 1 | foreach ($messages as $message) { |
|||
180 | 1 | $length = strlen($message); |
|||
181 | 1 | $this->writeInteger($fileHandle, $length); |
|||
182 | 1 | $this->writeInteger($fileHandle, $offset); |
|||
183 | 1 | $offset += $length + 1; |
|||
184 | } |
||||
185 | |||||
186 | // source messages |
||||
187 | 1 | foreach (array_keys($messages) as $id) { |
|||
188 | 1 | $this->writeString($fileHandle, $id); |
|||
189 | } |
||||
190 | |||||
191 | // target messages |
||||
192 | 1 | foreach ($messages as $message) { |
|||
193 | 1 | $this->writeString($fileHandle, $message); |
|||
194 | } |
||||
195 | |||||
196 | 1 | @flock($fileHandle, LOCK_UN); |
|||
0 ignored issues
–
show
It seems like you do not handle an error condition for
flock() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
197 | 1 | @fclose($fileHandle); |
|||
0 ignored issues
–
show
It seems like you do not handle an error condition for
fclose() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
198 | } |
||||
199 | |||||
200 | /** |
||||
201 | * Reads one or several bytes. |
||||
202 | * @param resource $fileHandle to read from |
||||
203 | * @param int $byteCount to be read |
||||
204 | * @return string bytes |
||||
205 | */ |
||||
206 | 2 | protected function readBytes($fileHandle, $byteCount = 1) |
|||
207 | { |
||||
208 | 2 | if ($byteCount > 0) { |
|||
209 | 2 | return fread($fileHandle, $byteCount); |
|||
210 | } |
||||
211 | |||||
212 | 2 | return null; |
|||
213 | } |
||||
214 | |||||
215 | /** |
||||
216 | * Write bytes. |
||||
217 | * @param resource $fileHandle to write to |
||||
218 | * @param string $bytes to be written |
||||
219 | * @return int how many bytes are written |
||||
220 | */ |
||||
221 | 1 | protected function writeBytes($fileHandle, $bytes) |
|||
222 | { |
||||
223 | 1 | return fwrite($fileHandle, $bytes); |
|||
224 | } |
||||
225 | |||||
226 | /** |
||||
227 | * Reads a 4-byte integer. |
||||
228 | * @param resource $fileHandle to read from |
||||
229 | * @return int the result |
||||
230 | */ |
||||
231 | 2 | protected function readInteger($fileHandle) |
|||
232 | { |
||||
233 | 2 | $array = unpack($this->useBigEndian ? 'N' : 'V', $this->readBytes($fileHandle, 4)); |
|||
234 | |||||
235 | 2 | return current($array); |
|||
236 | } |
||||
237 | |||||
238 | /** |
||||
239 | * Writes a 4-byte integer. |
||||
240 | * @param resource $fileHandle to write to |
||||
241 | * @param int $integer to be written |
||||
242 | * @return int how many bytes are written |
||||
243 | */ |
||||
244 | 1 | protected function writeInteger($fileHandle, $integer) |
|||
245 | { |
||||
246 | 1 | return $this->writeBytes($fileHandle, pack($this->useBigEndian ? 'N' : 'V', (int) $integer)); |
|||
247 | } |
||||
248 | |||||
249 | /** |
||||
250 | * Reads a string. |
||||
251 | * @param resource $fileHandle file handle |
||||
252 | * @param int $length of the string |
||||
253 | * @param int|null $offset of the string in the file. If null, it reads from the current position. |
||||
254 | * @return string the result |
||||
255 | */ |
||||
256 | 2 | protected function readString($fileHandle, $length, $offset = null) |
|||
257 | { |
||||
258 | 2 | if ($offset !== null) { |
|||
259 | 2 | fseek($fileHandle, $offset); |
|||
260 | } |
||||
261 | |||||
262 | 2 | return $this->readBytes($fileHandle, $length); |
|||
263 | } |
||||
264 | |||||
265 | /** |
||||
266 | * Writes a string. |
||||
267 | * @param resource $fileHandle to write to |
||||
268 | * @param string $string to be written |
||||
269 | * @return int how many bytes are written |
||||
270 | */ |
||||
271 | 1 | protected function writeString($fileHandle, $string) |
|||
272 | { |
||||
273 | 1 | return $this->writeBytes($fileHandle, $string . "\0"); |
|||
274 | } |
||||
275 | } |
||||
276 |
If you suppress an error, we recommend checking for the error condition explicitly: