1<?php
2
3/**
4 * SimplePie
5 *
6 * A PHP-Based RSS and Atom Feed Framework.
7 * Takes the hard work out of managing a complete RSS/Atom solution.
8 *
9 * Copyright (c) 2004-2022, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without modification, are
13 * permitted provided that the following conditions are met:
14 *
15 * 	* Redistributions of source code must retain the above copyright notice, this list of
16 * 	  conditions and the following disclaimer.
17 *
18 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
19 * 	  of conditions and the following disclaimer in the documentation and/or other materials
20 * 	  provided with the distribution.
21 *
22 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
23 * 	  to endorse or promote products derived from this software without specific prior
24 * 	  written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
27 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
28 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
29 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
33 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 *
36 * @package SimplePie
37 * @copyright 2004-2016 Ryan Parman, Sam Sneddon, Ryan McCue
38 * @author Ryan Parman
39 * @author Sam Sneddon
40 * @author Ryan McCue
41 * @link http://simplepie.org/ SimplePie
42 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
43 */
44
45namespace SimplePie;
46
47/**
48 * Decode 'gzip' encoded HTTP data
49 *
50 * @package SimplePie
51 * @subpackage HTTP
52 * @link http://www.gzip.org/format.txt
53 */
54class Gzdecode
55{
56    /**
57     * Compressed data
58     *
59     * @access private
60     * @var string
61     * @see gzdecode::$data
62     */
63    public $compressed_data;
64
65    /**
66     * Size of compressed data
67     *
68     * @access private
69     * @var int
70     */
71    public $compressed_size;
72
73    /**
74     * Minimum size of a valid gzip string
75     *
76     * @access private
77     * @var int
78     */
79    public $min_compressed_size = 18;
80
81    /**
82     * Current position of pointer
83     *
84     * @access private
85     * @var int
86     */
87    public $position = 0;
88
89    /**
90     * Flags (FLG)
91     *
92     * @access private
93     * @var int
94     */
95    public $flags;
96
97    /**
98     * Uncompressed data
99     *
100     * @access public
101     * @see gzdecode::$compressed_data
102     * @var string
103     */
104    public $data;
105
106    /**
107     * Modified time
108     *
109     * @access public
110     * @var int
111     */
112    public $MTIME;
113
114    /**
115     * Extra Flags
116     *
117     * @access public
118     * @var int
119     */
120    public $XFL;
121
122    /**
123     * Operating System
124     *
125     * @access public
126     * @var int
127     */
128    public $OS;
129
130    /**
131     * Subfield ID 1
132     *
133     * @access public
134     * @see gzdecode::$extra_field
135     * @see gzdecode::$SI2
136     * @var string
137     */
138    public $SI1;
139
140    /**
141     * Subfield ID 2
142     *
143     * @access public
144     * @see gzdecode::$extra_field
145     * @see gzdecode::$SI1
146     * @var string
147     */
148    public $SI2;
149
150    /**
151     * Extra field content
152     *
153     * @access public
154     * @see gzdecode::$SI1
155     * @see gzdecode::$SI2
156     * @var string
157     */
158    public $extra_field;
159
160    /**
161     * Original filename
162     *
163     * @access public
164     * @var string
165     */
166    public $filename;
167
168    /**
169     * Human readable comment
170     *
171     * @access public
172     * @var string
173     */
174    public $comment;
175
176    /**
177     * Don't allow anything to be set
178     *
179     * @param string $name
180     * @param mixed $value
181     */
182    public function __set($name, $value)
183    {
184        throw new Exception("Cannot write property $name");
185    }
186
187    /**
188     * Set the compressed string and related properties
189     *
190     * @param string $data
191     */
192    public function __construct($data)
193    {
194        $this->compressed_data = $data;
195        $this->compressed_size = strlen($data);
196    }
197
198    /**
199     * Decode the GZIP stream
200     *
201     * @return bool Successfulness
202     */
203    public function parse()
204    {
205        if ($this->compressed_size >= $this->min_compressed_size) {
206            $len = 0;
207
208            // Check ID1, ID2, and CM
209            if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08") {
210                return false;
211            }
212
213            // Get the FLG (FLaGs)
214            $this->flags = ord($this->compressed_data[3]);
215
216            // FLG bits above (1 << 4) are reserved
217            if ($this->flags > 0x1F) {
218                return false;
219            }
220
221            // Advance the pointer after the above
222            $this->position += 4;
223
224            // MTIME
225            $mtime = substr($this->compressed_data, $this->position, 4);
226            // Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness
227            if (current(unpack('S', "\x00\x01")) === 1) {
228                $mtime = strrev($mtime);
229            }
230            $this->MTIME = current(unpack('l', $mtime));
231            $this->position += 4;
232
233            // Get the XFL (eXtra FLags)
234            $this->XFL = ord($this->compressed_data[$this->position++]);
235
236            // Get the OS (Operating System)
237            $this->OS = ord($this->compressed_data[$this->position++]);
238
239            // Parse the FEXTRA
240            if ($this->flags & 4) {
241                // Read subfield IDs
242                $this->SI1 = $this->compressed_data[$this->position++];
243                $this->SI2 = $this->compressed_data[$this->position++];
244
245                // SI2 set to zero is reserved for future use
246                if ($this->SI2 === "\x00") {
247                    return false;
248                }
249
250                // Get the length of the extra field
251                $len = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
252                $this->position += 2;
253
254                // Check the length of the string is still valid
255                $this->min_compressed_size += $len + 4;
256                if ($this->compressed_size >= $this->min_compressed_size) {
257                    // Set the extra field to the given data
258                    $this->extra_field = substr($this->compressed_data, $this->position, $len);
259                    $this->position += $len;
260                } else {
261                    return false;
262                }
263            }
264
265            // Parse the FNAME
266            if ($this->flags & 8) {
267                // Get the length of the filename
268                $len = strcspn($this->compressed_data, "\x00", $this->position);
269
270                // Check the length of the string is still valid
271                $this->min_compressed_size += $len + 1;
272                if ($this->compressed_size >= $this->min_compressed_size) {
273                    // Set the original filename to the given string
274                    $this->filename = substr($this->compressed_data, $this->position, $len);
275                    $this->position += $len + 1;
276                } else {
277                    return false;
278                }
279            }
280
281            // Parse the FCOMMENT
282            if ($this->flags & 16) {
283                // Get the length of the comment
284                $len = strcspn($this->compressed_data, "\x00", $this->position);
285
286                // Check the length of the string is still valid
287                $this->min_compressed_size += $len + 1;
288                if ($this->compressed_size >= $this->min_compressed_size) {
289                    // Set the original comment to the given string
290                    $this->comment = substr($this->compressed_data, $this->position, $len);
291                    $this->position += $len + 1;
292                } else {
293                    return false;
294                }
295            }
296
297            // Parse the FHCRC
298            if ($this->flags & 2) {
299                // Check the length of the string is still valid
300                $this->min_compressed_size += $len + 2;
301                if ($this->compressed_size >= $this->min_compressed_size) {
302                    // Read the CRC
303                    $crc = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
304
305                    // Check the CRC matches
306                    if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) {
307                        $this->position += 2;
308                    } else {
309                        return false;
310                    }
311                } else {
312                    return false;
313                }
314            }
315
316            // Decompress the actual data
317            if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) {
318                return false;
319            }
320
321            $this->position = $this->compressed_size - 8;
322
323            // Check CRC of data
324            $crc = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
325            $this->position += 4;
326            /*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc))
327            {
328                return false;
329            }*/
330
331            // Check ISIZE of data
332            $isize = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
333            $this->position += 4;
334            if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) {
335                return false;
336            }
337
338            // Wow, against all odds, we've actually got a valid gzip string
339            return true;
340        }
341
342        return false;
343    }
344}
345
346class_alias('SimplePie\Gzdecode', 'SimplePie_gzdecode');
347