1<?php
2/**
3 * @package     Joomla.Platform
4 * @subpackage  Archive
5 *
6 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
7 * @license     GNU General Public License version 2 or later; see LICENSE
8 */
9
10defined('JPATH_PLATFORM') or die;
11
12jimport('joomla.filesystem.file');
13
14/**
15 * Gzip format adapter for the JArchive class
16 *
17 * This class is inspired from and draws heavily in code and concept from the Compress package of
18 * The Horde Project <https://www.horde.org>
19 *
20 * @contributor  Michael Slusarz <slusarz@horde.org>
21 * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
22 *
23 * @since       1.5
24 * @deprecated  4.0 use the Joomla\Archive\Gzip class instead
25 */
26class JArchiveGzip implements JArchiveExtractable
27{
28	/**
29	 * Gzip file flags.
30	 *
31	 * @var    array
32	 * @since  1.5
33	 */
34	private $_flags = array('FTEXT' => 0x01, 'FHCRC' => 0x02, 'FEXTRA' => 0x04, 'FNAME' => 0x08, 'FCOMMENT' => 0x10);
35
36	/**
37	 * Gzip file data buffer
38	 *
39	 * @var    string
40	 * @since  1.5
41	 */
42	private $_data = null;
43
44	/**
45	 * Extract a Gzip compressed file to a given path
46	 *
47	 * @param   string  $archive      Path to ZIP archive to extract
48	 * @param   string  $destination  Path to extract archive to
49	 * @param   array   $options      Extraction options [unused]
50	 *
51	 * @return  boolean  True if successful
52	 *
53	 * @since   1.5
54	 * @throws  RuntimeException
55	 */
56	public function extract($archive, $destination, array $options = array())
57	{
58		$this->_data = null;
59
60		if (!extension_loaded('zlib'))
61		{
62			return $this->raiseWarning(100, 'The zlib extension is not available.');
63		}
64
65		if (isset($options['use_streams']) && $options['use_streams'] != false)
66		{
67			return $this->extractStream($archive, $destination, $options);
68		}
69
70		$this->_data = file_get_contents($archive);
71
72		if (!$this->_data)
73		{
74			return $this->raiseWarning(100, 'Unable to read archive');
75		}
76
77		$position = $this->_getFilePosition();
78		$buffer = gzinflate(substr($this->_data, $position, strlen($this->_data) - $position));
79
80		if (empty($buffer))
81		{
82			return $this->raiseWarning(100, 'Unable to decompress data');
83		}
84
85		if (JFile::write($destination, $buffer) === false)
86		{
87			return $this->raiseWarning(100, 'Unable to write archive');
88		}
89
90		return true;
91	}
92
93	/**
94	 * Method to extract archive using stream objects
95	 *
96	 * @param   string  $archive      Path to ZIP archive to extract
97	 * @param   string  $destination  Path to extract archive to
98	 * @param   array   $options      Extraction options [unused]
99	 *
100	 * @return  boolean  True if successful
101	 *
102	 * @since   3.6.0
103	 */
104	protected function extractStream($archive, $destination, $options = array())
105	{
106		// New style! streams!
107		$input = JFactory::getStream();
108
109		// Use gz
110		$input->set('processingmethod', 'gz');
111
112		if (!$input->open($archive))
113		{
114			return $this->raiseWarning(100, 'Unable to read archive (gz)');
115		}
116
117		$output = JFactory::getStream();
118
119		if (!$output->open($destination, 'w'))
120		{
121			$input->close();
122
123			return $this->raiseWarning(100, 'Unable to write archive (gz)');
124		}
125
126		do
127		{
128			$this->_data = $input->read($input->get('chunksize', 8196));
129
130			if ($this->_data && !$output->write($this->_data))
131			{
132				$input->close();
133
134				return $this->raiseWarning(100, 'Unable to write file (gz)');
135			}
136		}
137
138		while ($this->_data);
139
140		$output->close();
141		$input->close();
142
143		return true;
144	}
145
146	/**
147	 * Temporary private method to isolate JError from the extract method
148	 * This code should be removed when JError is removed.
149	 *
150	 * @param   int     $code  The application-internal error code for this error
151	 * @param   string  $msg   The error message, which may also be shown the user if need be.
152	 *
153	 * @return  JException  JException instance if JError class exists
154	 *
155	 * @since   3.6.0
156	 * @throws  RuntimeException if JError class does not exist
157	 */
158	private function raiseWarning($code, $msg)
159	{
160		if (class_exists('JError'))
161		{
162			return JError::raiseWarning($code, $msg);
163		}
164
165		throw new RuntimeException($msg);
166	}
167
168	/**
169	 * Tests whether this adapter can unpack files on this computer.
170	 *
171	 * @return  boolean  True if supported
172	 *
173	 * @since   2.5.0
174	 */
175	public static function isSupported()
176	{
177		return extension_loaded('zlib');
178	}
179
180	/**
181	 * Get file data offset for archive
182	 *
183	 * @return  integer  Data position marker for archive
184	 *
185	 * @since   1.5
186	 * @throws  RuntimeException
187	 */
188	public function _getFilePosition()
189	{
190		// Gzipped file... unpack it first
191		$position = 0;
192		$info = @ unpack('CCM/CFLG/VTime/CXFL/COS', substr($this->_data, $position + 2));
193
194		if (!$info)
195		{
196			return $this->raiseWarning(100, 'Unable to decompress data.');
197		}
198
199		$position += 10;
200
201		if ($info['FLG'] & $this->_flags['FEXTRA'])
202		{
203			$XLEN = unpack('vLength', substr($this->_data, $position + 0, 2));
204			$XLEN = $XLEN['Length'];
205			$position += $XLEN + 2;
206		}
207
208		if ($info['FLG'] & $this->_flags['FNAME'])
209		{
210			$filenamePos = strpos($this->_data, "\x0", $position);
211			$position = $filenamePos + 1;
212		}
213
214		if ($info['FLG'] & $this->_flags['FCOMMENT'])
215		{
216			$commentPos = strpos($this->_data, "\x0", $position);
217			$position = $commentPos + 1;
218		}
219
220		if ($info['FLG'] & $this->_flags['FHCRC'])
221		{
222			$hcrc = unpack('vCRC', substr($this->_data, $position + 0, 2));
223			$hcrc = $hcrc['CRC'];
224			$position += 2;
225		}
226
227		return $position;
228	}
229}
230