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