QGIS API Documentation 4.1.0-Master (60fea48833c)
Loading...
Searching...
No Matches
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
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 "qgsziputils.h"
17
18#include <fstream>
19#include <iostream>
20#include <zip.h>
21#include <zlib.h>
22
23#include "qgslogger.h"
24#include "qgsmessagelog.h"
25
26#include <QDir>
27#include <QFileInfo>
28#include <QString>
29
30using namespace Qt::StringLiterals;
31
32bool QgsZipUtils::isZipFile( const QString &filename )
33{
34 return QFileInfo( filename ).suffix().compare( "qgz"_L1, 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() ) + u"/"_s ).startsWith( QDir( dir ).absolutePath() + u"/"_s ) )
96 {
97 QgsMessageLog::logMessage( QObject::tr( "Skipped file %1 outside of the directory %2" ).arg( newFile.absoluteFilePath(), QDir( dir ).absolutePath() ) );
98 continue;
99 }
100
101 // Create path for a new file if it does not exist.
102 if ( !newFile.absoluteDir().exists() )
103 {
104 if ( !QDir( dir ).mkpath( newFile.absolutePath() ) )
105 QgsMessageLog::logMessage( QObject::tr( "Failed to create a subdirectory %1/%2" ).arg( dir ).arg( fileName ) );
106 }
107
108 QFile outFile( newFile.absoluteFilePath() );
109 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
110 {
111 QgsMessageLog::logMessage( QObject::tr( "Could not write to %1" ).arg( newFile.absoluteFilePath() ) );
112 }
113 else
114 {
115 outFile.write( buf.get(), len );
116 }
117 zip_fclose( file );
118 files.append( newFile.absoluteFilePath() );
119 }
120 else
121 {
122 zip_fclose( file );
123 QgsMessageLog::logMessage( QObject::tr( "Error reading file: '%1'" ).arg( zip_strerror( z ) ) );
124 return false;
125 }
126 }
127 }
128 else
129 {
130 zip_close( z );
131 QgsMessageLog::logMessage( QObject::tr( "Error getting files: '%1'" ).arg( zip_strerror( z ) ) );
132 return false;
133 }
134
135 zip_close( z );
136 }
137 else
138 {
139 QgsMessageLog::logMessage( QObject::tr( "Error opening zip archive: '%1' (Error code: %2)" ).arg( z ? zip_strerror( z ) : zipFilename ).arg( rc ) );
140 return false;
141 }
142
143 return true;
144}
145
146bool QgsZipUtils::zip( const QString &zipFilename, const QStringList &files, bool overwrite )
147{
148 if ( zipFilename.isEmpty() )
149 {
150 QgsMessageLog::logMessage( QObject::tr( "Error zip filename is empty" ) );
151 return false;
152 }
153
154 int rc = 0;
155 const QByteArray zipFileNamePtr = zipFilename.toUtf8();
156 struct zip *z = zip_open( zipFileNamePtr.constData(), overwrite ? ( ZIP_CREATE | ZIP_TRUNCATE ) : ZIP_CREATE, &rc );
157
158 if ( rc == ZIP_ER_OK && z )
159 {
160 for ( const auto &file : files )
161 {
162 const QFileInfo fileInfo( file );
163 if ( !fileInfo.exists() )
164 {
165 QgsMessageLog::logMessage( QObject::tr( "Error input file does not exist: '%1'" ).arg( file ) );
166 zip_close( z );
167 return false;
168 }
169
170 const QByteArray fileNamePtr = file.toUtf8();
171 zip_source *src = zip_source_file( z, fileNamePtr.constData(), 0, 0 );
172 if ( src )
173 {
174 const QByteArray fileInfoPtr = fileInfo.fileName().toUtf8();
175#if LIBZIP_VERSION_MAJOR < 1
176 rc = ( int ) zip_add( z, fileInfoPtr.constData(), src );
177#else
178 rc = ( int ) zip_file_add( z, fileInfoPtr.constData(), src, 0 );
179#endif
180 if ( rc == -1 )
181 {
182 QgsMessageLog::logMessage( QObject::tr( "Error adding file '%1': %2" ).arg( file, zip_strerror( z ) ) );
183 zip_close( z );
184 return false;
185 }
186 }
187 else
188 {
189 QgsMessageLog::logMessage( QObject::tr( "Error creating data source '%1': %2" ).arg( file, zip_strerror( z ) ) );
190 zip_close( z );
191 return false;
192 }
193 }
194
195 zip_close( z );
196 }
197 else
198 {
199 QgsMessageLog::logMessage( QObject::tr( "Error creating zip archive '%1': %2" ).arg( zipFilename, z ? zip_strerror( z ) : zipFilename ) );
200 return false;
201 }
202
203 return true;
204}
205
206bool QgsZipUtils::decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
207{
208 return decodeGzip( bytesIn.constData(), bytesIn.count(), bytesOut );
209}
210
211bool QgsZipUtils::decodeGzip( const char *bytesIn, std::size_t size, QByteArray &bytesOut )
212{
213 unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn ) );
214 uint bytesInLeft = static_cast<uint>( size );
215
216 const uint CHUNK = 16384;
217 unsigned char out[CHUNK];
218 const int DEC_MAGIC_NUM_FOR_GZIP = 16;
219
220 // allocate inflate state
221 z_stream strm;
222 strm.zalloc = Z_NULL;
223 strm.zfree = Z_NULL;
224 strm.opaque = Z_NULL;
225 strm.avail_in = 0;
226 strm.next_in = Z_NULL;
227
228 int ret = inflateInit2( &strm, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP );
229 if ( ret != Z_OK )
230 {
231 inflateEnd( &strm );
232 return false;
233 }
234
235 while ( ret != Z_STREAM_END ) // done when inflate() says it's done
236 {
237 // prepare next chunk
238 const uint bytesToProcess = std::min( CHUNK, bytesInLeft );
239 strm.next_in = bytesInPtr;
240 strm.avail_in = bytesToProcess;
241 bytesInPtr += bytesToProcess;
242 bytesInLeft -= bytesToProcess;
243
244 if ( bytesToProcess == 0 )
245 break; // we end with an error - no more data but inflate() wants more data
246
247 // run inflate() on input until output buffer not full
248 do
249 {
250 strm.avail_out = CHUNK;
251 strm.next_out = out;
252 ret = inflate( &strm, Z_NO_FLUSH );
253 Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
254 if ( ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR )
255 {
256 inflateEnd( &strm );
257 return false;
258 }
259 const unsigned have = CHUNK - strm.avail_out;
260 bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
261 } while ( strm.avail_out == 0 );
262 }
263
264 inflateEnd( &strm );
265 return ret == Z_STREAM_END;
266}
267
268bool QgsZipUtils::encodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
269{
270 unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
271 const uint bytesInLeft = static_cast<uint>( bytesIn.count() );
272
273 const uint CHUNK = 16384;
274 unsigned char out[CHUNK];
275 const int DEC_MAGIC_NUM_FOR_GZIP = 16;
276
277 // allocate deflate state
278 z_stream strm;
279 strm.zalloc = Z_NULL;
280 strm.zfree = Z_NULL;
281 strm.opaque = Z_NULL;
282
283 int ret = deflateInit2( &strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP, 8, Z_DEFAULT_STRATEGY );
284 if ( ret != Z_OK )
285 return false;
286
287 strm.avail_in = bytesInLeft;
288 strm.next_in = bytesInPtr;
289
290 // run deflate() on input until output buffer not full, finish
291 // compression if all of source has been read in
292 do
293 {
294 strm.avail_out = CHUNK;
295 strm.next_out = out;
296 ret = deflate( &strm, Z_FINISH ); // no bad return value
297 Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
298
299 const unsigned have = CHUNK - strm.avail_out;
300 bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
301 } while ( strm.avail_out == 0 );
302 Q_ASSERT( ret == Z_STREAM_END ); // stream will be complete
303
304 // clean up and return
305 deflateEnd( &strm );
306 return true;
307}
308
309const QStringList QgsZipUtils::files( const QString &zip )
310{
311 if ( zip.isEmpty() && !QFileInfo::exists( zip ) )
312 {
313 return QStringList();
314 }
315 QStringList files;
316
317 int rc = 0;
318 const QByteArray fileNamePtr = zip.toUtf8();
319 struct zip *z = zip_open( fileNamePtr.constData(), 0, &rc );
320
321 if ( rc == ZIP_ER_OK && z )
322 {
323 const int count = zip_get_num_entries( z, ZIP_FL_UNCHANGED );
324 if ( count != -1 )
325 {
326 struct zip_stat stat;
327
328 for ( int i = 0; i < count; i++ )
329 {
330 zip_stat_index( z, i, 0, &stat );
331 files << QString( stat.name );
332 }
333 }
334
335 zip_close( z );
336 }
337
338 return files;
339}
340
341bool QgsZipUtils::extractFileFromZip( const QString &zipFilename, const QString &filenameInZip, QByteArray &bytesOut )
342{
343 if ( !QFileInfo::exists( zipFilename ) )
344 {
345 QgsMessageLog::logMessage( QObject::tr( "Error zip file does not exist: '%1'" ).arg( zipFilename ) );
346 return false;
347 }
348
349 if ( filenameInZip.isEmpty() )
350 {
351 QgsMessageLog::logMessage( QObject::tr( "Error file name in zip is empty" ) );
352 return false;
353 }
354
355 int err = 0;
356 const QByteArray zipFilenamePtr = zipFilename.toUtf8();
357 struct zip *z = zip_open( zipFilenamePtr.constData(), 0, &err );
358 if ( !z )
359 {
360 zip_error_t error;
361 zip_error_init_with_code( &error, err );
362 QgsMessageLog::logMessage( QObject::tr( "Error opening zip archive '%1': %2" ).arg( zipFilename, zip_error_strerror( &error ) ) );
363 zip_error_fini( &error );
364 return false;
365 }
366
367 const QByteArray filenameInZipPtr = filenameInZip.toUtf8();
368 struct zip_stat st;
369 zip_stat_init( &st );
370 if ( zip_stat( z, filenameInZipPtr.constData(), 0, &st ) != 0 )
371 {
372 QgsMessageLog::logMessage( QObject::tr( "File '%1' not found in zip archive '%2': %3" ).arg( filenameInZip, zipFilename, zip_strerror( z ) ) );
373 zip_close( z );
374 return false;
375 }
376
377 zip_file *zf = zip_fopen( z, filenameInZipPtr.constData(), 0 );
378 if ( !zf )
379 {
380 QgsMessageLog::logMessage( QObject::tr( "Could not open file '%1' in zip archive '%2': %3" ).arg( filenameInZip, zipFilename, zip_strerror( z ) ) );
381 zip_close( z );
382 return false;
383 }
384
385 bytesOut.resize( static_cast<int>( st.size ) );
386 zip_int64_t readBytes = zip_fread( zf, bytesOut.data(), st.size );
387
388 zip_fclose( zf );
389 zip_close( z );
390
391 // If successful, the number of bytes actually read is returned.
392 if ( static_cast<zip_uint64_t>( readBytes ) != st.size )
393 {
394 QgsMessageLog::logMessage( QObject::tr( "Error reading file '%1' from zip archive '%2'." ).arg( filenameInZip, zipFilename ) );
395 return false;
396 }
397
398 return true;
399}
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
static bool extractFileFromZip(const QString &zipFilename, const QString &filenameInZip, QByteArray &bytesOut)
Extracts a file from a zip archive, returns true on success.
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.