QGIS API Documentation  3.25.0-Master (6b426f5f8a)
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.blo[email protected]
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 
32 bool QgsZipUtils::isZipFile( const QString &filename )
33 {
34  return QFileInfo( filename ).suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) == 0;
35 }
36 
37 bool QgsZipUtils::unzip( const QString &zipFilename, const QString &dir, QStringList &files )
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(), ZIP_CHECKCONS, &rc );
70 
71  if ( rc == ZIP_ER_OK && z )
72  {
73  const int count = zip_get_num_files( z );
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 
135 bool 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 
195 bool QgsZipUtils::decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
196 {
197  return decodeGzip( bytesIn.constData(), bytesIn.count(), bytesOut );
198 }
199 
200 bool 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 
255 bool 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 }
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.
Definition: qgsziputils.cpp:32
CORE_EXPORT bool unzip(const QString &zip, const QString &dir, QStringList &files)
Unzip a zip file in an output directory.
Definition: qgsziputils.cpp:37
CORE_EXPORT bool zip(const QString &zip, const QStringList &files)
Zip the list of files in the zip file.
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.