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