QGIS API Documentation 3.43.0-Master (5250e42f050)
qgsziputils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsziputils.cpp
3 ---------------------
4 begin : Jul 2017
5 copyright : (C) 2017 by Paul Blottiere
6 email : paul.blottiere@oslandia.com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <fstream>
17
18#include <QFileInfo>
19#include <QDir>
20
21#include "zip.h"
22
23#include <zlib.h>
24
25#include "qgsmessagelog.h"
26#include "qgsziputils.h"
27#include "qgslogger.h"
28
29#include <iostream>
30
31
32bool QgsZipUtils::isZipFile( const QString &filename )
33{
34 return QFileInfo( filename ).suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) == 0;
35}
36
37bool QgsZipUtils::unzip( const QString &zipFilename, const QString &dir, QStringList &files, bool checkConsistency )
38{
39 files.clear();
40
41 if ( !QFileInfo::exists( zipFilename ) )
42 {
43 QgsMessageLog::logMessage( QObject::tr( "Error zip file does not exist: '%1'" ).arg( zipFilename ) );
44 return false;
45 }
46 else if ( zipFilename.isEmpty() )
47 {
48 QgsMessageLog::logMessage( QObject::tr( "Error zip filename is empty" ) );
49 return false;
50 }
51 else if ( !QDir( dir ).exists( dir ) )
52 {
53 QgsMessageLog::logMessage( QObject::tr( "Error output dir does not exist: '%1'" ).arg( dir ) );
54 return false;
55 }
56 else if ( !QFileInfo( dir ).isDir() )
57 {
58 QgsMessageLog::logMessage( QObject::tr( "Error output dir is not a directory: '%1'" ).arg( dir ) );
59 return false;
60 }
61 else if ( !QFileInfo( dir ).isWritable() )
62 {
63 QgsMessageLog::logMessage( QObject::tr( "Error output dir is not writable: '%1'" ).arg( dir ) );
64 return false;
65 }
66
67 int rc = 0;
68 const QByteArray fileNamePtr = zipFilename.toUtf8();
69 struct zip *z = zip_open( fileNamePtr.constData(), checkConsistency ? ZIP_CHECKCONS : 0, &rc );
70
71 if ( rc == ZIP_ER_OK && z )
72 {
73 const int count = zip_get_num_entries( z, ZIP_FL_UNCHANGED );
74 if ( count != -1 )
75 {
76 struct zip_stat stat;
77
78 for ( int i = 0; i < count; i++ )
79 {
80 zip_stat_index( z, i, 0, &stat );
81 const size_t len = stat.size;
82
83 struct zip_file *file = zip_fopen_index( z, i, 0 );
84 const std::unique_ptr< char[] > buf( new char[len] );
85 if ( zip_fread( file, buf.get(), len ) != -1 )
86 {
87 const QString fileName( stat.name );
88 if ( fileName.endsWith( "/" ) )
89 {
90 continue;
91 }
92
93 const QFileInfo newFile( QDir( dir ), fileName );
94
95 if ( !QString( QDir::cleanPath( newFile.absolutePath() ) + QStringLiteral( "/" ) ).startsWith( QDir( dir ).absolutePath() + QStringLiteral( "/" ) ) )
96 {
97 QgsMessageLog::logMessage( QObject::tr( "Skipped file %1 outside of the directory %2" ).arg(
98 newFile.absoluteFilePath(),
99 QDir( dir ).absolutePath()
100 ) );
101 continue;
102 }
103
104 // Create path for a new file if it does not exist.
105 if ( !newFile.absoluteDir().exists() )
106 {
107 if ( !QDir( dir ).mkpath( newFile.absolutePath() ) )
108 QgsMessageLog::logMessage( QObject::tr( "Failed to create a subdirectory %1/%2" ).arg( dir ).arg( fileName ) );
109 }
110
111 QFile outFile( newFile.absoluteFilePath() );
112 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
113 {
114 QgsMessageLog::logMessage( QObject::tr( "Could not write to %1" ).arg( newFile.absoluteFilePath() ) );
115 }
116 else
117 {
118 outFile.write( buf.get(), len );
119 }
120 zip_fclose( file );
121 files.append( newFile.absoluteFilePath() );
122 }
123 else
124 {
125 zip_fclose( file );
126 QgsMessageLog::logMessage( QObject::tr( "Error reading file: '%1'" ).arg( zip_strerror( z ) ) );
127 return false;
128 }
129 }
130 }
131 else
132 {
133 zip_close( z );
134 QgsMessageLog::logMessage( QObject::tr( "Error getting files: '%1'" ).arg( zip_strerror( z ) ) );
135 return false;
136 }
137
138 zip_close( z );
139 }
140 else
141 {
142 QgsMessageLog::logMessage( QObject::tr( "Error opening zip archive: '%1' (Error code: %2)" ).arg( z ? zip_strerror( z ) : zipFilename ).arg( rc ) );
143 return false;
144 }
145
146 return true;
147}
148
149bool QgsZipUtils::zip( const QString &zipFilename, const QStringList &files, bool overwrite )
150{
151 if ( zipFilename.isEmpty() )
152 {
153 QgsMessageLog::logMessage( QObject::tr( "Error zip filename is empty" ) );
154 return false;
155 }
156
157 int rc = 0;
158 const QByteArray zipFileNamePtr = zipFilename.toUtf8();
159 struct zip *z = zip_open( zipFileNamePtr.constData(), overwrite ? ( ZIP_CREATE | ZIP_TRUNCATE ) : ZIP_CREATE, &rc );
160
161 if ( rc == ZIP_ER_OK && z )
162 {
163 for ( const auto &file : files )
164 {
165 const QFileInfo fileInfo( file );
166 if ( !fileInfo.exists() )
167 {
168 QgsMessageLog::logMessage( QObject::tr( "Error input file does not exist: '%1'" ).arg( file ) );
169 zip_close( z );
170 return false;
171 }
172
173 const QByteArray fileNamePtr = file.toUtf8();
174 zip_source *src = zip_source_file( z, fileNamePtr.constData(), 0, 0 );
175 if ( src )
176 {
177 const QByteArray fileInfoPtr = fileInfo.fileName().toUtf8();
178#if LIBZIP_VERSION_MAJOR < 1
179 rc = ( int ) zip_add( z, fileInfoPtr.constData(), src );
180#else
181 rc = ( int ) zip_file_add( z, fileInfoPtr.constData(), src, 0 );
182#endif
183 if ( rc == -1 )
184 {
185 QgsMessageLog::logMessage( QObject::tr( "Error adding file '%1': %2" ).arg( file, zip_strerror( z ) ) );
186 zip_close( z );
187 return false;
188 }
189 }
190 else
191 {
192 QgsMessageLog::logMessage( QObject::tr( "Error creating data source '%1': %2" ).arg( file, zip_strerror( z ) ) );
193 zip_close( z );
194 return false;
195 }
196 }
197
198 zip_close( z );
199 }
200 else
201 {
202 QgsMessageLog::logMessage( QObject::tr( "Error creating zip archive '%1': %2" ).arg( zipFilename, z ? zip_strerror( z ) : zipFilename ) );
203 return false;
204 }
205
206 return true;
207}
208
209bool QgsZipUtils::decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
210{
211 return decodeGzip( bytesIn.constData(), bytesIn.count(), bytesOut );
212}
213
214bool QgsZipUtils::decodeGzip( const char *bytesIn, std::size_t size, QByteArray &bytesOut )
215{
216 unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn ) );
217 uint bytesInLeft = static_cast<uint>( size );
218
219 const uint CHUNK = 16384;
220 unsigned char out[CHUNK];
221 const int DEC_MAGIC_NUM_FOR_GZIP = 16;
222
223 // allocate inflate state
224 z_stream strm;
225 strm.zalloc = Z_NULL;
226 strm.zfree = Z_NULL;
227 strm.opaque = Z_NULL;
228 strm.avail_in = 0;
229 strm.next_in = Z_NULL;
230
231 int ret = inflateInit2( &strm, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP );
232 if ( ret != Z_OK )
233 {
234 inflateEnd( &strm );
235 return false;
236 }
237
238 while ( ret != Z_STREAM_END ) // done when inflate() says it's done
239 {
240 // prepare next chunk
241 const uint bytesToProcess = std::min( CHUNK, bytesInLeft );
242 strm.next_in = bytesInPtr;
243 strm.avail_in = bytesToProcess;
244 bytesInPtr += bytesToProcess;
245 bytesInLeft -= bytesToProcess;
246
247 if ( bytesToProcess == 0 )
248 break; // we end with an error - no more data but inflate() wants more data
249
250 // run inflate() on input until output buffer not full
251 do
252 {
253 strm.avail_out = CHUNK;
254 strm.next_out = out;
255 ret = inflate( &strm, Z_NO_FLUSH );
256 Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
257 if ( ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR )
258 {
259 inflateEnd( &strm );
260 return false;
261 }
262 const unsigned have = CHUNK - strm.avail_out;
263 bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
264 }
265 while ( strm.avail_out == 0 );
266 }
267
268 inflateEnd( &strm );
269 return ret == Z_STREAM_END;
270}
271
272bool QgsZipUtils::encodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
273{
274 unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
275 const uint bytesInLeft = static_cast<uint>( bytesIn.count() );
276
277 const uint CHUNK = 16384;
278 unsigned char out[CHUNK];
279 const int DEC_MAGIC_NUM_FOR_GZIP = 16;
280
281 // allocate deflate state
282 z_stream strm;
283 strm.zalloc = Z_NULL;
284 strm.zfree = Z_NULL;
285 strm.opaque = Z_NULL;
286
287 int ret = deflateInit2( &strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP, 8, Z_DEFAULT_STRATEGY );
288 if ( ret != Z_OK )
289 return false;
290
291 strm.avail_in = bytesInLeft;
292 strm.next_in = bytesInPtr;
293
294 // run deflate() on input until output buffer not full, finish
295 // compression if all of source has been read in
296 do
297 {
298 strm.avail_out = CHUNK;
299 strm.next_out = out;
300 ret = deflate( &strm, Z_FINISH ); // no bad return value
301 Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
302
303 const unsigned have = CHUNK - strm.avail_out;
304 bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
305 }
306 while ( strm.avail_out == 0 );
307 Q_ASSERT( ret == Z_STREAM_END ); // stream will be complete
308
309 // clean up and return
310 deflateEnd( &strm );
311 return true;
312}
313
314const QStringList QgsZipUtils::files( const QString &zip )
315{
316 if ( zip.isEmpty() && !QFileInfo::exists( zip ) )
317 {
318 return QStringList();
319 }
320 QStringList files;
321
322 int rc = 0;
323 const QByteArray fileNamePtr = zip.toUtf8();
324 struct zip *z = zip_open( fileNamePtr.constData(), 0, &rc );
325
326 if ( rc == ZIP_ER_OK && z )
327 {
328 const int count = zip_get_num_entries( z, ZIP_FL_UNCHANGED );
329 if ( count != -1 )
330 {
331 struct zip_stat stat;
332
333 for ( int i = 0; i < count; i++ )
334 {
335 zip_stat_index( z, i, 0, &stat );
336 files << QString( stat.name );
337 }
338 }
339
340 zip_close( z );
341 }
342
343 return files;
344}
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
static bool isZipFile(const QString &filename)
Returns true if the file name is a zipped file ( i.e with a '.qgz' extension, false otherwise.
static bool zip(const QString &zip, const QStringList &files, bool overwrite=false)
Zip the list of files in the zip file.
static bool unzip(const QString &zip, const QString &dir, QStringList &files, bool checkConsistency=true)
Unzip a zip file in an output directory.
static bool decodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Decodes gzip byte stream, returns true on success.
static bool encodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Encodes gzip byte stream, returns true on success.
static const QStringList files(const QString &zip)
Returns the list of files within a zip file.