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.
12 #include "utils/URIUtils.h"
13 #include "utils/log.h"
17 #define ZIP_CACHE_LIMIT 4*1024*1024
19 using namespace XFILE
;
23 m_szStringBuffer
= NULL
;
24 m_szStartOfStringBuffer
= NULL
;
25 m_iDataInStringBuffer
= 0;
32 delete[] m_szStringBuffer
;
36 bool CZipFile::Open(const CURL
&url
)
38 const std::string
& strOpts
= url
.GetOptions();
41 if (!g_ZipManager
.GetZipEntry(url2
,mZipItem
))
44 if ((mZipItem
.flags
& 64) == 64)
46 CLog::Log(LOGERROR
,"FileZip: encrypted file, not supported!");
50 if ((mZipItem
.method
!= 8) && (mZipItem
.method
!= 0))
52 CLog::Log(LOGERROR
,"FileZip: unsupported compression method!");
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
))
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());
74 mFile
.Seek(mZipItem
.offset
,SEEK_SET
);
75 return InitDecompress();
78 bool CZipFile::InitDecompress()
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!");
96 m_ZStream
.next_in
= (Bytef
*)m_szBuffer
;
97 m_ZStream
.avail_in
= 0;
98 m_ZStream
.total_out
= 0;
103 int64_t CZipFile::GetLength()
105 return mZipItem
.usize
;
108 int64_t CZipFile::GetPosition()
111 return mFile
.GetPosition();
116 int64_t CZipFile::Seek(int64_t iFilePosition
, int iWhence
)
119 return mFile
.Seek(iFilePosition
,iWhence
);
120 if (mZipItem
.method
== 0) // this is easy
126 if (iFilePosition
> mZipItem
.usize
)
128 m_iFilePos
= iFilePosition
;
129 m_iZipFilePos
= m_iFilePos
;
130 iResult
= mFile
.Seek(iFilePosition
+mZipItem
.offset
,SEEK_SET
)-mZipItem
.offset
;
135 if (m_iFilePos
+iFilePosition
> mZipItem
.usize
)
137 m_iFilePos
+= iFilePosition
;
138 m_iZipFilePos
= m_iFilePos
;
139 iResult
= mFile
.Seek(iFilePosition
,SEEK_CUR
)-mZipItem
.offset
;
144 if (iFilePosition
> mZipItem
.usize
)
146 m_iFilePos
= mZipItem
.usize
+iFilePosition
;
147 m_iZipFilePos
= m_iFilePos
;
148 iResult
= mFile
.Seek(mZipItem
.offset
+mZipItem
.usize
+iFilePosition
,SEEK_SET
)-mZipItem
.offset
;
156 // here goes the stupid part..
157 if (mZipItem
.method
== 8)
159 static const int blockSize
= 128 * 1024;
160 std::vector
<char> buf(blockSize
);
164 if (iFilePosition
== m_iFilePos
)
165 return m_iFilePos
; // mp3reader does this lots-of-times
166 if (iFilePosition
> mZipItem
.usize
|| iFilePosition
< 0)
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
)
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
)
190 return Seek(iFilePosition
-m_iFilePos
,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
)
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
)
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
)
228 bool CZipFile::Exists(const CURL
& url
)
231 if (g_ZipManager
.GetZipEntry(url
,item
))
236 int CZipFile::Stat(struct __stat64
*buffer
)
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
;
256 int CZipFile::Stat(const CURL
& url
, struct __stat64
* buffer
)
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
266 buffer
->st_mode
= _S_IFDIR
;
275 buffer
->st_atime
= buffer
->st_ctime
= mZipItem
.mod_time
;
276 buffer
->st_size
= mZipItem
.usize
;
280 ssize_t
CZipFile::Read(void* lpBuf
, size_t uiBufSize
)
282 if (uiBufSize
> SSIZE_MAX
)
283 uiBufSize
= SSIZE_MAX
;
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
);
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
;
315 if (!m_ZStream
.avail_in
)
317 if (!FillBuffer()) // eof!
319 iDecompressed
= m_ZStream
.total_out
-prevOut
;
324 int iMessage
= inflate(&m_ZStream
,Z_SYNC_FLUSH
);
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
;
344 return 0; // we are past eof, this shouldn't happen but test anyway
346 ssize_t iResult
= mFile
.Read(lpBuf
,uiBufSize
);
349 m_iZipFilePos
+= iResult
;
350 m_iFilePos
+= iResult
;
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
);
365 bool CZipFile::FillBuffer()
367 ssize_t sToRead
= 65535;
368 if (m_iZipFilePos
+65535 > mZipItem
.csize
)
369 sToRead
= mZipItem
.csize
-m_iZipFilePos
;
372 return false; // eof!
374 if (mFile
.Read(m_szBuffer
,sToRead
) != sToRead
)
376 m_ZStream
.avail_in
= static_cast<unsigned int>(sToRead
);
377 m_ZStream
.next_in
= reinterpret_cast<Byte
*>(m_szBuffer
);
378 m_iZipFilePos
+= sToRead
;
382 void CZipFile::DestroyBuffer(void* lpBuffer
, int iBufSize
)
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
);
396 int CZipFile::UnpackFromMemory(std::string
& strDest
, const std::string
& strInput
, bool isGZ
)
400 while( iPos
+LHDR_SIZE
< strInput
.size() || 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
411 if (mZipItem
.header
!= ZIP_LOCAL_HEADER
)
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
;
423 if (i
+ DREC_SIZE
> strInput
.size())
425 CLog::Log(LOGERROR
, "FileZip: extended local header expected, but not present!");
428 if ((strInput
[i
] == 0x50) && (strInput
[i
+ 1] == 0x4b) &&
429 (strInput
[i
+ 2] == 0x07) && (strInput
[i
+ 3] == 0x08))
430 break; // header found
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())
450 // we have a file - fill the buffer
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];
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
;
465 strDest
.reserve(mZipItem
.usize
);
466 temp
= new char[mZipItem
.usize
+1];
467 toRead
= mZipItem
.usize
;
470 while((iCurrResult
= static_cast<int>(Read(temp
, toRead
))) > 0)
472 strDest
.append(temp
,temp
+iCurrResult
);
473 iResult
+= iCurrResult
;
477 iPos
+= LHDR_SIZE
+mZipItem
.flength
+mZipItem
.elength
+mZipItem
.csize
;
485 bool CZipFile::DecompressGzip(const std::string
& in
, std::string
& out
)
487 const int windowBits
= MAX_WBITS
+ 16;
490 strm
.zalloc
= Z_NULL
;
492 strm
.opaque
= Z_NULL
;
494 int err
= inflateInit2(&strm
, windowBits
);
497 CLog::Log(LOGERROR
, "FileZip: zlib error {}", err
);
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
);
520 CLog::Log(LOGERROR
, "FileZip: failed to decompress. zlib error {}", err
);
524 int read
= bufferSize
- strm
.avail_out
;
525 out
.append((char*)buffer
, read
);
527 while (strm
.avail_out
== 0);