QGIS API Documentation 3.43.0-Master (b60ef06885e)
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 const QFileInfo newFile( QDir( dir ), fileName );
89
90 if ( !QString( QDir::cleanPath( newFile.absolutePath() ) + QStringLiteral( "/" ) ).startsWith( QDir( dir ).absolutePath() + QStringLiteral( "/" ) ) )
91 {
92 QgsMessageLog::logMessage( QObject::tr( "Skipped file %1 outside of the directory %2" ).arg(
93 newFile.absoluteFilePath(),
94 QDir( dir ).absolutePath()
95 ) );
96 continue;
97 }
98
99 // Create path for a new file if it does not exist.
100 if ( !newFile.absoluteDir().exists() )
101 {
102 if ( !QDir( dir ).mkpath( newFile.absolutePath() ) )
103 QgsMessageLog::logMessage( QObject::tr( "Failed to create a subdirectory %1/%2" ).arg( dir ).arg( fileName ) );
104 }
105
106 QFile outFile( newFile.absoluteFilePath() );
107 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
108 {
109 QgsMessageLog::logMessage( QObject::tr( "Could not write to %1" ).arg( newFile.absoluteFilePath() ) );
110 }
111 else
112 {
113 outFile.write( buf.get(), len );
114 }
115 zip_fclose( file );
116 files.append( newFile.absoluteFilePath() );
117 }
118 else
119 {
120 zip_fclose( file );
121 QgsMessageLog::logMessage( QObject::tr( "Error reading file: '%1'" ).arg( zip_strerror( z ) ) );
122 return false;
123 }
124 }
125 }
126 else
127 {
128 zip_close( z );
129 QgsMessageLog::logMessage( QObject::tr( "Error getting files: '%1'" ).arg( zip_strerror( z ) ) );
130 return false;
131 }
132
133 zip_close( z );
134 }
135 else
136 {
137 QgsMessageLog::logMessage( QObject::tr( "Error opening zip archive: '%1' (Error code: %2)" ).arg( z ? zip_strerror( z ) : zipFilename ).arg( rc ) );
138 return false;
139 }
140
141 return true;
142}
143
144bool QgsZipUtils::zip( const QString &zipFilename, const QStringList &files )
145{
146 if ( zipFilename.isEmpty() )
147 {
148 QgsMessageLog::logMessage( QObject::tr( "Error zip filename is empty" ) );
149 return false;
150 }
151
152 int rc = 0;
153 const QByteArray zipFileNamePtr = zipFilename.toUtf8();
154 struct zip *z = zip_open( zipFileNamePtr.constData(), ZIP_CREATE, &rc );
155
156 if ( rc == ZIP_ER_OK && z )
157 {
158 for ( const auto &file : files )
159 {
160 const QFileInfo fileInfo( file );
161 if ( !fileInfo.exists() )
162 {
163 QgsMessageLog::logMessage( QObject::tr( "Error input file does not exist: '%1'" ).arg( file ) );
164 zip_close( z );
165 return false;
166 }
167
168 const QByteArray fileNamePtr = file.toUtf8();
169 zip_source *src = zip_source_file( z, fileNamePtr.constData(), 0, 0 );
170 if ( src )
171 {
172 const QByteArray fileInfoPtr = fileInfo.fileName().toUtf8();
173#if LIBZIP_VERSION_MAJOR < 1
174 rc = ( int ) zip_add( z, fileInfoPtr.constData(), src );
175#else
176 rc = ( int ) zip_file_add( z, fileInfoPtr.constData(), src, 0 );
177#endif
178 if ( rc == -1 )
179 {
180 QgsMessageLog::logMessage( QObject::tr( "Error adding file '%1': %2" ).arg( file, zip_strerror( z ) ) );
181 zip_close( z );
182 return false;
183 }
184 }
185 else
186 {
187 QgsMessageLog::logMessage( QObject::tr( "Error creating data source '%1': %2" ).arg( file, zip_strerror( z ) ) );
188 zip_close( z );
189 return false;
190 }
191 }
192
193 zip_close( z );
194 }
195 else
196 {
197 QgsMessageLog::logMessage( QObject::tr( "Error creating zip archive '%1': %2" ).arg( zipFilename, zip_strerror( z ) ) );
198 return false;
199 }
200
201 return true;
202}
203
204bool QgsZipUtils::decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
205{
206 return decodeGzip( bytesIn.constData(), bytesIn.count(), bytesOut );
207}
208
209bool QgsZipUtils::decodeGzip( const char *bytesIn, std::size_t size, QByteArray &bytesOut )
210{
211 unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn ) );
212 uint bytesInLeft = static_cast<uint>( size );
213
214 const uint CHUNK = 16384;
215 unsigned char out[CHUNK];
216 const int DEC_MAGIC_NUM_FOR_GZIP = 16;
217
218 // allocate inflate state
219 z_stream strm;
220 strm.zalloc = Z_NULL;
221 strm.zfree = Z_NULL;
222 strm.opaque = Z_NULL;
223 strm.avail_in = 0;
224 strm.next_in = Z_NULL;
225
226 int ret = inflateInit2( &strm, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP );
227 if ( ret != Z_OK )
228 {
229 inflateEnd( &strm );
230 return false;
231 }
232
233 while ( ret != Z_STREAM_END ) // done when inflate() says it's done
234 {
235 // prepare next chunk
236 const uint bytesToProcess = std::min( CHUNK, bytesInLeft );
237 strm.next_in = bytesInPtr;
238 strm.avail_in = bytesToProcess;
239 bytesInPtr += bytesToProcess;
240 bytesInLeft -= bytesToProcess;
241
242 if ( bytesToProcess == 0 )
243 break; // we end with an error - no more data but inflate() wants more data
244
245 // run inflate() on input until output buffer not full
246 do
247 {
248 strm.avail_out = CHUNK;
249 strm.next_out = out;
250 ret = inflate( &strm, Z_NO_FLUSH );
251 Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
252 if ( ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR )
253 {
254 inflateEnd( &strm );
255 return false;
256 }
257 const unsigned have = CHUNK - strm.avail_out;
258 bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
259 }
260 while ( strm.avail_out == 0 );
261 }
262
263 inflateEnd( &strm );
264 return ret == Z_STREAM_END;
265}
266
267bool QgsZipUtils::encodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
268{
269 unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
270 const uint bytesInLeft = static_cast<uint>( bytesIn.count() );
271
272 const uint CHUNK = 16384;
273 unsigned char out[CHUNK];
274 const int DEC_MAGIC_NUM_FOR_GZIP = 16;
275
276 // allocate deflate state
277 z_stream strm;
278 strm.zalloc = Z_NULL;
279 strm.zfree = Z_NULL;
280 strm.opaque = Z_NULL;
281
282 int ret = deflateInit2( &strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP, 8, Z_DEFAULT_STRATEGY );
283 if ( ret != Z_OK )
284 return false;
285
286 strm.avail_in = bytesInLeft;
287 strm.next_in = bytesInPtr;
288
289 // run deflate() on input until output buffer not full, finish
290 // compression if all of source has been read in
291 do
292 {
293 strm.avail_out = CHUNK;
294 strm.next_out = out;
295 ret = deflate( &strm, Z_FINISH ); // no bad return value
296 Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
297
298 const unsigned have = CHUNK - strm.avail_out;
299 bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
300 }
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}
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)
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.