2828import sys
2929import time
3030import warnings
31-
3231from libarchive import _libarchive
3332from io import StringIO
3433
35- PY3 = sys .version_info [0 ] = = 3
34+ PY3 = sys .version_info [0 ] > = 3
3635
3736# Suggested block size for libarchive. Libarchive may adjust it.
3837BLOCK_SIZE = 10240
8887class EOF (Exception ):
8988 '''Raised by ArchiveInfo.from_archive() when unable to read the next
9089 archive header.'''
90+
9191 pass
9292
93+ def version ():
94+ '''Returns the version of the libarchive library.'''
95+ return _libarchive .archive_version_string ().split ()[1 ]
96+
9397
9498def get_error (archive ):
9599 '''Retrieves the last error description for the given archive instance.'''
@@ -106,7 +110,7 @@ def call_and_check(func, archive, *args):
106110 elif ret == _libarchive .ARCHIVE_EOF :
107111 raise EOF ()
108112 else :
109- raise Exception ('Fatal error executing function, message is: %s.' % get_error (archive ))
113+ raise Exception ('Problem executing function, message is: %s.' % get_error (archive ))
110114
111115
112116def get_func (name , items , index ):
@@ -142,7 +146,7 @@ def is_archive_name(filename, formats=None):
142146 return format
143147
144148
145- def is_archive (f , formats = (None , ), filters = (None , )):
149+ def is_archive (f , formats = (None ,), filters = (None ,)):
146150 '''Check to see if the given file is actually an archive. The format parameter
147151 can be used to specify which archive format is acceptable. If ommitted, all supported
148152 archive formats will be checked. It opens the file using libarchive. If no error is
@@ -155,8 +159,10 @@ def is_archive(f, formats=(None, ), filters=(None, )):
155159
156160 This function will return True if the file can be opened as an archive using the given
157161 format(s)/filter(s).'''
162+ need_close = False
158163 if isinstance (f , str ):
159- f = open (f , 'r' )
164+ f = open (f , 'rb' )
165+ need_close = True
160166 a = _libarchive .archive_read_new ()
161167 for format in formats :
162168 format = get_func (format , FORMATS , 0 )
@@ -177,11 +183,13 @@ def is_archive(f, formats=(None, ), filters=(None, )):
177183 finally :
178184 _libarchive .archive_read_close (a )
179185 _libarchive .archive_read_free (a )
180- f .close ()
186+ if need_close :
187+ f .close ()
181188
182189
183190class EntryReadStream (object ):
184191 '''A file-like object for reading an entry from the archive.'''
192+
185193 def __init__ (self , archive , size ):
186194 self .archive = archive
187195 self .closed = False
@@ -241,6 +249,7 @@ class EntryWriteStream(object):
241249 If the size is known ahead of time and provided, then the file contents
242250 are not buffered but flushed directly to the archive. If size is omitted,
243251 then the file contents are buffered and flushed in the close() method.'''
252+
244253 def __init__ (self , archive , pathname , size = None ):
245254 self .archive = archive
246255 self .entry = Entry (pathname = pathname , mtime = time .time (), mode = stat .S_IFREG )
@@ -274,7 +283,7 @@ def write(self, data):
274283 if self .buffer :
275284 self .buffer .write (data )
276285 else :
277- _libarchive .archive_write_data_from_str (self .archive ._a , data .encode ('utf-8' ))
286+ _libarchive .archive_write_data_from_str (self .archive ._a , data .encode (ENCODING ))
278287 self .bytes += len (data )
279288
280289 def close (self ):
@@ -283,7 +292,7 @@ def close(self):
283292 if self .buffer :
284293 self .entry .size = self .buffer .tell ()
285294 self .entry .to_archive (self .archive )
286- _libarchive .archive_write_data_from_str (self .archive ._a , self .buffer .getvalue ().encode ('utf-8' ))
295+ _libarchive .archive_write_data_from_str (self .archive ._a , self .buffer .getvalue ().encode (ENCODING ))
287296 _libarchive .archive_write_finish_entry (self .archive ._a )
288297
289298 # Call archive.close() with _defer True to let it know we have been
@@ -295,14 +304,18 @@ def close(self):
295304
296305class Entry (object ):
297306 '''An entry within an archive. Represents the header data and it's location within the archive.'''
307+
298308 def __init__ (self , pathname = None , size = None , mtime = None , mode = None , hpos = None , encoding = ENCODING ):
309+
299310 self .pathname = pathname
300311 self .size = size
301312 self .mtime = mtime
302313 self .mode = mode
303314 self .hpos = hpos
304315 self .encoding = encoding
305316
317+ self .symlink = ""
318+
306319 @property
307320 def header_position (self ):
308321 return self .hpos
@@ -328,6 +341,11 @@ def from_archive(cls, archive, encoding=ENCODING):
328341 mode = mode ,
329342 hpos = archive .header_position ,
330343 )
344+
345+ if entry .issym ():
346+ symLinkPath = _libarchive .archive_entry_symlink (e )
347+ entry .symlink = symLinkPath
348+
331349 finally :
332350 _libarchive .archive_entry_free (e )
333351 return entry
@@ -356,6 +374,8 @@ def from_file(cls, f, entry=None, encoding=ENCODING):
356374 entry .size = getattr (f , 'size' , 0 )
357375 entry .mtime = getattr (f , 'mtime' , time .time ())
358376 entry .mode = stat .S_IFREG
377+
378+
359379 return entry
360380
361381 def to_archive (self , archive ):
@@ -370,8 +390,12 @@ def to_archive(self, archive):
370390 _libarchive .archive_entry_set_perm (e , stat .S_IMODE (self .mode ))
371391 _libarchive .archive_entry_set_size (e , self .size )
372392 _libarchive .archive_entry_set_mtime (e , self .mtime , 0 )
393+
394+ if stat .S_ISLNK (self .mode ):
395+ _libarchive .archive_entry_set_symlink (e , self .symlink )
396+
373397 call_and_check (_libarchive .archive_write_header , archive ._a , archive ._a , e )
374- #self.hpos = archive.header_position
398+
375399 finally :
376400 _libarchive .archive_entry_free (e )
377401
@@ -397,11 +421,23 @@ def isblk(self):
397421class Archive (object ):
398422 '''A low-level archive reader which provides forward-only iteration. Consider
399423 this a light-weight pythonic libarchive wrapper.'''
400- def __init__ (self , f , mode = 'r' , format = None , filter = None , entry_class = Entry , encoding = ENCODING , blocksize = BLOCK_SIZE ):
424+
425+ def __init__ (
426+ self ,
427+ f ,
428+ mode = 'r' ,
429+ format = None ,
430+ filter = None ,
431+ entry_class = Entry ,
432+ encoding = ENCODING ,
433+ blocksize = BLOCK_SIZE ,
434+ password = None ,
435+ ):
401436 assert mode in ('r' , 'w' , 'wb' , 'a' ), 'Mode should be "r", "w", "wb", or "a".'
402437 self ._stream = None
403438 self .encoding = encoding
404439 self .blocksize = blocksize
440+ self .password = password
405441 if isinstance (f , str ):
406442 self .filename = f
407443 f = open (f , mode )
@@ -462,16 +498,28 @@ def __exit__(self, type, value, traceback):
462498 def __del__ (self ):
463499 self .close ()
464500
501+ def set_initial_options (self ):
502+ pass
503+
465504 def init (self ):
466505 if self .mode == 'r' :
467506 self ._a = _libarchive .archive_read_new ()
468507 else :
469508 self ._a = _libarchive .archive_write_new ()
470509 self .format_func (self ._a )
471510 self .filter_func (self ._a )
511+ self .set_initial_options ()
472512 if self .mode == 'r' :
513+ if self .password :
514+ if isinstance (self .password , list ):
515+ for pwd in self .password :
516+ self .add_passphrase (pwd )
517+ else :
518+ self .add_passphrase (self .password )
473519 call_and_check (_libarchive .archive_read_open_fd , self ._a , self ._a , self .f .fileno (), self .blocksize )
474520 else :
521+ if self .password :
522+ self .set_passphrase (self .password )
475523 call_and_check (_libarchive .archive_write_open_fd , self ._a , self ._a , self .f .fileno ())
476524
477525 def denit (self ):
@@ -509,9 +557,11 @@ def close(self, _defer=False):
509557 if getattr (self .f , 'closed' , False ):
510558 return
511559 # Flush it if not read-only...
512- if self .f .mode != 'r' and self .f .mode != 'rb' :
513- self .f .flush ()
514- os .fsync (self .f .fileno ())
560+ if hasattr (self .f , "mode" ) and self .f .mode != 'r' and self .f .mode != 'rb' :
561+ if hasattr (self .f , "flush" ):
562+ self .f .flush ()
563+ if hasattr (self .f , "fileno" ):
564+ os .fsync (self .f .fileno ())
515565 # and then close it, if we opened it...
516566 if getattr (self , '_close' , None ):
517567 self .f .close ()
@@ -533,7 +583,7 @@ def readpath(self, f):
533583 '''Write current archive entry contents to file. f can be a file-like object or
534584 a path.'''
535585 if isinstance (f , str ):
536- basedir = os .path .basename (f )
586+ basedir = os .path .dirname (f )
537587 if not os .path .exists (basedir ):
538588 os .makedirs (basedir )
539589 f = open (f , 'w' )
@@ -547,7 +597,8 @@ def readstream(self, size):
547597 def write (self , member , data = None ):
548598 '''Writes a string buffer to the archive as the given entry.'''
549599 if isinstance (member , str ):
550- member = self .entry_class (pathname = member , encoding = self .encoding )
600+ member = self .entry_class (pathname = member , encoding = self .encoding ,
601+ mtime = time .time (), mode = stat .S_IFREG )
551602 if data :
552603 member .size = len (data )
553604 member .to_archive (self )
@@ -557,7 +608,7 @@ def write(self, member, data=None):
557608 if isinstance (data , bytes ):
558609 result = _libarchive .archive_write_data_from_str (self ._a , data )
559610 else :
560- result = _libarchive .archive_write_data_from_str (self ._a , data .encode ('utf8' ))
611+ result = _libarchive .archive_write_data_from_str (self ._a , data .encode (self . encoding ))
561612 else :
562613 result = _libarchive .archive_write_data_from_str (self ._a , data )
563614 _libarchive .archive_write_finish_entry (self ._a )
@@ -594,13 +645,22 @@ def printlist(self, s=sys.stdout):
594645 s .write (entry .pathname )
595646 s .flush ()
596647
648+ def add_passphrase (self , password ):
649+ '''Adds a password to the archive.'''
650+ _libarchive .archive_read_add_passphrase (self ._a , password )
651+
652+ def set_passphrase (self , password ):
653+ '''Sets a password for the archive.'''
654+ _libarchive .archive_write_set_passphrase (self ._a , password )
655+
597656
598657class SeekableArchive (Archive ):
599658 '''A class that provides random-access to archive entries. It does this by using one
600659 or many Archive instances to seek to the correct location. The best performance will
601660 occur when reading archive entries in the order in which they appear in the archive.
602661 Reading out of order will cause the archive to be closed and opened each time a
603662 reverse seek is needed.'''
663+
604664 def __init__ (self , f , ** kwargs ):
605665 self ._stream = None
606666 # Convert file to open file. We need this to reopen the archive.
0 commit comments