Merge pull request #26013 from ksooo/estuary-recordings-info
[xbmc.git] / xbmc / filesystem / ZipFile.cpp
blobeb3d95309853f3d102bceae4665f8e800eb9b758
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "ZipFile.h"
11 #include "URL.h"
12 #include "utils/URIUtils.h"
13 #include "utils/log.h"
15 #include <sys/stat.h>
17 #define ZIP_CACHE_LIMIT 4*1024*1024
19 using namespace XFILE;
21 CZipFile::CZipFile()
23 m_szStringBuffer = NULL;
24 m_szStartOfStringBuffer = NULL;
25 m_iDataInStringBuffer = 0;
26 m_bCached = false;
27 m_iRead = -1;
30 CZipFile::~CZipFile()
32 delete[] m_szStringBuffer;
33 Close();
36 bool CZipFile::Open(const CURL&url)
38 const std::string& strOpts = url.GetOptions();
39 CURL url2(url);
40 url2.SetOptions("");
41 if (!g_ZipManager.GetZipEntry(url2,mZipItem))
42 return false;
44 if ((mZipItem.flags & 64) == 64)
46 CLog::Log(LOGERROR,"FileZip: encrypted file, not supported!");
47 return false;
50 if ((mZipItem.method != 8) && (mZipItem.method != 0))
52 CLog::Log(LOGERROR,"FileZip: unsupported compression method!");
53 return false;
56 if (mZipItem.method != 0 && mZipItem.usize > ZIP_CACHE_LIMIT && strOpts != "?cache=no")
58 if (!CFile::Exists("special://temp/" + URIUtils::GetFileName(url2)))
60 url2.SetOptions("?cache=no");
61 const CURL pathToUrl("special://temp/" + URIUtils::GetFileName(url2));
62 if (!CFile::Copy(url2, pathToUrl))
63 return false;
65 m_bCached = true;
66 return mFile.Open("special://temp/" + URIUtils::GetFileName(url2));
69 if (!mFile.Open(url.GetHostName())) // this is the zip-file, always open binary
71 CLog::Log(LOGERROR, "FileZip: unable to open zip file {}!", url.GetHostName());
72 return false;
74 mFile.Seek(mZipItem.offset,SEEK_SET);
75 return InitDecompress();
78 bool CZipFile::InitDecompress()
80 m_iRead = 1;
81 m_iFilePos = 0;
82 m_iZipFilePos = 0;
83 m_iAvailBuffer = 0;
84 m_bFlush = false;
85 m_ZStream.zalloc = Z_NULL;
86 m_ZStream.zfree = Z_NULL;
87 m_ZStream.opaque = Z_NULL;
88 if( mZipItem.method != 0 )
90 if (inflateInit2(&m_ZStream,-MAX_WBITS) != Z_OK)
92 CLog::Log(LOGERROR,"FileZip: error initializing zlib!");
93 return false;
96 m_ZStream.next_in = (Bytef*)m_szBuffer;
97 m_ZStream.avail_in = 0;
98 m_ZStream.total_out = 0;
100 return true;
103 int64_t CZipFile::GetLength()
105 return mZipItem.usize;
108 int64_t CZipFile::GetPosition()
110 if (m_bCached)
111 return mFile.GetPosition();
113 return m_iFilePos;
116 int64_t CZipFile::Seek(int64_t iFilePosition, int iWhence)
118 if (m_bCached)
119 return mFile.Seek(iFilePosition,iWhence);
120 if (mZipItem.method == 0) // this is easy
122 int64_t iResult;
123 switch (iWhence)
125 case SEEK_SET:
126 if (iFilePosition > mZipItem.usize)
127 return -1;
128 m_iFilePos = iFilePosition;
129 m_iZipFilePos = m_iFilePos;
130 iResult = mFile.Seek(iFilePosition+mZipItem.offset,SEEK_SET)-mZipItem.offset;
131 return iResult;
132 break;
134 case SEEK_CUR:
135 if (m_iFilePos+iFilePosition > mZipItem.usize)
136 return -1;
137 m_iFilePos += iFilePosition;
138 m_iZipFilePos = m_iFilePos;
139 iResult = mFile.Seek(iFilePosition,SEEK_CUR)-mZipItem.offset;
140 return iResult;
141 break;
143 case SEEK_END:
144 if (iFilePosition > mZipItem.usize)
145 return -1;
146 m_iFilePos = mZipItem.usize+iFilePosition;
147 m_iZipFilePos = m_iFilePos;
148 iResult = mFile.Seek(mZipItem.offset+mZipItem.usize+iFilePosition,SEEK_SET)-mZipItem.offset;
149 return iResult;
150 break;
151 default:
152 return -1;
156 // here goes the stupid part..
157 if (mZipItem.method == 8)
159 static const int blockSize = 128 * 1024;
160 std::vector<char> buf(blockSize);
161 switch (iWhence)
163 case SEEK_SET:
164 if (iFilePosition == m_iFilePos)
165 return m_iFilePos; // mp3reader does this lots-of-times
166 if (iFilePosition > mZipItem.usize || iFilePosition < 0)
167 return -1;
168 // read until position in 128k blocks.. only way to do it due to format.
169 // can't start in the middle of data since then we'd have no clue where
170 // we are in uncompressed data..
171 if (iFilePosition < m_iFilePos)
173 m_iFilePos = 0;
174 m_iZipFilePos = 0;
175 inflateEnd(&m_ZStream);
176 inflateInit2(&m_ZStream,-MAX_WBITS); // simply restart zlib
177 mFile.Seek(mZipItem.offset,SEEK_SET);
178 m_ZStream.next_in = (Bytef*)m_szBuffer;
179 m_ZStream.avail_in = 0;
180 m_ZStream.total_out = 0;
181 while (m_iFilePos < iFilePosition)
183 ssize_t iToRead = (iFilePosition - m_iFilePos) > blockSize ? blockSize : iFilePosition - m_iFilePos;
184 if (Read(buf.data(), iToRead) != iToRead)
185 return -1;
187 return m_iFilePos;
189 else // seek forward
190 return Seek(iFilePosition-m_iFilePos,SEEK_CUR);
191 break;
193 case SEEK_CUR:
194 if (iFilePosition < 0)
195 return Seek(m_iFilePos+iFilePosition,SEEK_SET); // can't rewind stream
196 // read until requested position, drop data
197 if (m_iFilePos+iFilePosition > mZipItem.usize)
198 return -1;
199 iFilePosition += m_iFilePos;
200 while (m_iFilePos < iFilePosition)
202 ssize_t iToRead = (iFilePosition - m_iFilePos)>blockSize ? blockSize : iFilePosition - m_iFilePos;
203 if (Read(buf.data(), iToRead) != iToRead)
204 return -1;
206 return m_iFilePos;
207 break;
209 case SEEK_END:
210 // now this is a nasty bastard, possibly takes lotsoftime
211 // uncompress, minding m_ZStream.total_out
213 while(static_cast<ssize_t>(m_ZStream.total_out) < mZipItem.usize+iFilePosition)
215 ssize_t iToRead = (mZipItem.usize + iFilePosition - m_ZStream.total_out > blockSize) ? blockSize : mZipItem.usize + iFilePosition - m_ZStream.total_out;
216 if (Read(buf.data(), iToRead) != iToRead)
217 return -1;
219 return m_iFilePos;
220 break;
221 default:
222 return -1;
225 return -1;
228 bool CZipFile::Exists(const CURL& url)
230 SZipEntry item;
231 if (g_ZipManager.GetZipEntry(url,item))
232 return true;
233 return false;
236 int CZipFile::Stat(struct __stat64 *buffer)
238 int ret;
239 struct tm tm = {};
241 ret = mFile.Stat(buffer);
242 tm.tm_sec = (mZipItem.mod_time & 0x1F) << 1;
243 tm.tm_min = (mZipItem.mod_time & 0x7E0) >> 5;
244 tm.tm_hour = (mZipItem.mod_time & 0xF800) >> 11;
245 tm.tm_mday = (mZipItem.mod_date & 0x1F);
246 tm.tm_mon = (mZipItem.mod_date & 0x1E0) >> 5;
247 tm.tm_year = (mZipItem.mod_date & 0xFE00) >> 9;
248 buffer->st_atime = buffer->st_ctime = buffer->st_mtime = mktime(&tm);
250 buffer->st_size = mZipItem.usize;
251 buffer->st_dev = (buffer->st_dev << 16) ^ (buffer->st_ino << 16);
252 buffer->st_ino ^= mZipItem.crc32;
253 return ret;
256 int CZipFile::Stat(const CURL& url, struct __stat64* buffer)
258 if (!buffer)
259 return -1;
261 if (!g_ZipManager.GetZipEntry(url, mZipItem))
263 if (url.GetFileName().empty() && CFile::Exists(url.GetHostName()))
264 { // when accessing the zip "root" recognize it as a directory
265 *buffer = {};
266 buffer->st_mode = _S_IFDIR;
267 return 0;
269 else
270 return -1;
273 *buffer = {};
274 buffer->st_gid = 0;
275 buffer->st_atime = buffer->st_ctime = mZipItem.mod_time;
276 buffer->st_size = mZipItem.usize;
277 return 0;
280 ssize_t CZipFile::Read(void* lpBuf, size_t uiBufSize)
282 if (uiBufSize > SSIZE_MAX)
283 uiBufSize = SSIZE_MAX;
285 if (m_bCached)
286 return mFile.Read(lpBuf,uiBufSize);
288 // flush what might be left in the string buffer
289 if (m_iDataInStringBuffer > 0)
291 size_t iMax = uiBufSize>m_iDataInStringBuffer?m_iDataInStringBuffer:uiBufSize;
292 memcpy(lpBuf,m_szStartOfStringBuffer,iMax);
293 uiBufSize -= iMax;
294 m_iDataInStringBuffer -= iMax;
296 if (mZipItem.method == 8) // deflated
298 uLong iDecompressed = 0;
299 uLong prevOut = m_ZStream.total_out;
300 while ((iDecompressed < uiBufSize) && ((m_iZipFilePos < mZipItem.csize) || (m_bFlush)))
302 m_ZStream.next_out = (Bytef*)(lpBuf)+iDecompressed;
303 m_ZStream.avail_out = static_cast<uInt>(uiBufSize-iDecompressed);
304 if (m_bFlush) // need to flush buffer !
306 int iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
307 m_bFlush = ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))?true:false;
308 if (!m_ZStream.avail_out) // flush filled buffer, get out of here
310 iDecompressed = m_ZStream.total_out-prevOut;
311 break;
315 if (!m_ZStream.avail_in)
317 if (!FillBuffer()) // eof!
319 iDecompressed = m_ZStream.total_out-prevOut;
320 break;
324 int iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
325 if (iMessage < 0)
327 Close();
328 return -1; // READ ERROR
331 m_bFlush = ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))?true:false; // more info in input buffer
333 iDecompressed = m_ZStream.total_out-prevOut;
335 m_iFilePos += iDecompressed;
336 return static_cast<unsigned int>(iDecompressed);
338 else if (mZipItem.method == 0) // uncompressed. just read from file, but mind our boundaries.
340 if (uiBufSize+m_iFilePos > mZipItem.csize)
341 uiBufSize = mZipItem.csize-m_iFilePos;
343 if (uiBufSize == 0)
344 return 0; // we are past eof, this shouldn't happen but test anyway
346 ssize_t iResult = mFile.Read(lpBuf,uiBufSize);
347 if (iResult < 0)
348 return -1;
349 m_iZipFilePos += iResult;
350 m_iFilePos += iResult;
351 return iResult;
353 else
354 return -1; // shouldn't happen. compression method checked in open
357 void CZipFile::Close()
359 if (mZipItem.method == 8 && !m_bCached && m_iRead != -1)
360 inflateEnd(&m_ZStream);
362 mFile.Close();
365 bool CZipFile::FillBuffer()
367 ssize_t sToRead = 65535;
368 if (m_iZipFilePos+65535 > mZipItem.csize)
369 sToRead = mZipItem.csize-m_iZipFilePos;
371 if (sToRead <= 0)
372 return false; // eof!
374 if (mFile.Read(m_szBuffer,sToRead) != sToRead)
375 return false;
376 m_ZStream.avail_in = static_cast<unsigned int>(sToRead);
377 m_ZStream.next_in = reinterpret_cast<Byte*>(m_szBuffer);
378 m_iZipFilePos += sToRead;
379 return true;
382 void CZipFile::DestroyBuffer(void* lpBuffer, int iBufSize)
384 if (!m_bFlush)
385 return;
386 int iMessage = Z_OK;
387 while ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))
389 m_ZStream.next_out = (Bytef*)lpBuffer;
390 m_ZStream.avail_out = iBufSize;
391 iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
393 m_bFlush = false;
396 int CZipFile::UnpackFromMemory(std::string& strDest, const std::string& strInput, bool isGZ)
398 unsigned int iPos=0;
399 int iResult=0;
400 while( iPos+LHDR_SIZE < strInput.size() || isGZ)
402 if (!isGZ)
404 CZipManager::readHeader(strInput.data()+iPos,mZipItem);
405 if (mZipItem.header == ZIP_DATA_RECORD_HEADER)
407 // this header concerns a file we already processed, so we can just skip it
408 iPos += DREC_SIZE;
409 continue;
411 if (mZipItem.header != ZIP_LOCAL_HEADER)
412 return iResult;
413 if( (mZipItem.flags & 8) == 8 )
415 // if an extended local header (=data record header) is present,
416 // the following fields are 0 in the local header and we need to read
417 // them from the extended local header
419 // search for the extended local header
420 unsigned int i = iPos + LHDR_SIZE + mZipItem.flength + mZipItem.elength;
421 while (1)
423 if (i + DREC_SIZE > strInput.size())
425 CLog::Log(LOGERROR, "FileZip: extended local header expected, but not present!");
426 return iResult;
428 if ((strInput[i] == 0x50) && (strInput[i + 1] == 0x4b) &&
429 (strInput[i + 2] == 0x07) && (strInput[i + 3] == 0x08))
430 break; // header found
431 i++;
433 // ZIP is little endian:
434 mZipItem.crc32 = static_cast<uint8_t>(strInput[i + 4]) |
435 static_cast<uint8_t>(strInput[i + 5]) << 8 |
436 static_cast<uint8_t>(strInput[i + 6]) << 16 |
437 static_cast<uint8_t>(strInput[i + 7]) << 24;
438 mZipItem.csize = static_cast<uint8_t>(strInput[i + 8]) |
439 static_cast<uint8_t>(strInput[i + 9]) << 8 |
440 static_cast<uint8_t>(strInput[i + 10]) << 16 |
441 static_cast<uint8_t>(strInput[i + 11]) << 24;
442 mZipItem.usize = static_cast<uint8_t>(strInput[i + 12]) |
443 static_cast<uint8_t>(strInput[i + 13]) << 8 |
444 static_cast<uint8_t>(strInput[i + 14]) << 16 |
445 static_cast<uint8_t>(strInput[i + 15]) << 24;
448 if (!InitDecompress())
449 return iResult;
450 // we have a file - fill the buffer
451 char* temp;
452 ssize_t toRead=0;
453 if (isGZ)
455 m_ZStream.avail_in = static_cast<unsigned int>(strInput.size());
456 m_ZStream.next_in = const_cast<Bytef*>((const Bytef*)strInput.data());
457 temp = new char[8192];
458 toRead = 8191;
460 else
462 m_ZStream.avail_in = mZipItem.csize;
463 m_ZStream.next_in = const_cast<Bytef*>((const Bytef*)strInput.data())+iPos+LHDR_SIZE+mZipItem.flength+mZipItem.elength;
464 // init m_zipitem
465 strDest.reserve(mZipItem.usize);
466 temp = new char[mZipItem.usize+1];
467 toRead = mZipItem.usize;
469 int iCurrResult;
470 while((iCurrResult = static_cast<int>(Read(temp, toRead))) > 0)
472 strDest.append(temp,temp+iCurrResult);
473 iResult += iCurrResult;
475 Close();
476 delete[] temp;
477 iPos += LHDR_SIZE+mZipItem.flength+mZipItem.elength+mZipItem.csize;
478 if (isGZ)
479 break;
482 return iResult;
485 bool CZipFile::DecompressGzip(const std::string& in, std::string& out)
487 const int windowBits = MAX_WBITS + 16;
489 z_stream strm;
490 strm.zalloc = Z_NULL;
491 strm.zfree = Z_NULL;
492 strm.opaque = Z_NULL;
494 int err = inflateInit2(&strm, windowBits);
495 if (err != Z_OK)
497 CLog::Log(LOGERROR, "FileZip: zlib error {}", err);
498 return false;
501 const int bufferSize = 16384;
502 unsigned char buffer[bufferSize];
504 strm.avail_in = static_cast<unsigned int>(in.size());
505 strm.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(in.c_str()));
509 strm.avail_out = bufferSize;
510 strm.next_out = buffer;
511 int err = inflate(&strm, Z_NO_FLUSH);
512 switch (err)
514 case Z_NEED_DICT:
515 err = Z_DATA_ERROR;
516 [[fallthrough]];
517 case Z_DATA_ERROR:
518 case Z_MEM_ERROR:
519 case Z_STREAM_ERROR:
520 CLog::Log(LOGERROR, "FileZip: failed to decompress. zlib error {}", err);
521 inflateEnd(&strm);
522 return false;
524 int read = bufferSize - strm.avail_out;
525 out.append((char*)buffer, read);
527 while (strm.avail_out == 0);
529 inflateEnd(&strm);
530 return true;