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