Skip to content

Commit 0b2eefb

Browse files
authored
Merge pull request #17 from Vadiml1024/extended
Add support for password protected archives
2 parents 300361c + 9d5895f commit 0b2eefb

12 files changed

Lines changed: 4857 additions & 2056 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ verify:
1111
install:
1212
python setup.py install
1313

14+
1415
publish:
1516
python setup.py register
1617
python setup.py sdist upload

libarchive/Makefile

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
CFLAGS = -g
2-
INCLUDE = -I/usr/include -I.
3-
LIBS = -larchive
2+
INCLUDE = -I/usr/local/include -I/usr/include -I.
3+
LIBS = -L /usr/local/lib -larchive
44

5-
PYVER ?= 2.7
5+
PYVER ?= 3.9
66

77
all: __libarchive.so
88

99
_libarchive_wrap.c: _libarchive.i
10-
swig -python -shadow _libarchive.i
10+
swig -python -Wall -shadow _libarchive.i
1111

1212
_libarchive_wrap.o: _libarchive_wrap.c
13-
${CC} -c ${CFLAGS} -fPIC ${INCLUDE} $$(python${PYVER}-config --cflags) _libarchive_wrap.c
13+
${CC} -c ${CFLAGS} -fPIC $$(python${PYVER}-config --cflags) _libarchive_wrap.c
1414

1515
__libarchive.so: _libarchive_wrap.o
16-
${CC} _libarchive_wrap.o -shared $$(python${PYVER}-config --ldflags) -o __libarchive.so ${LIBS}
16+
${CC} _libarchive_wrap.o -shared $$(python${PYVER}-config --ldflags) -Wl,-soname=__libarchive.so -o __libarchive.so ${LIBS}
1717

1818
clean:
19-
rm -f *.o *.so *.pyc
19+
rm -f *.o *.so *.pyc

libarchive/__init__.py

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@
2828
import sys
2929
import time
3030
import warnings
31-
3231
from libarchive import _libarchive
3332
from 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.
3837
BLOCK_SIZE = 10240
@@ -88,8 +87,13 @@
8887
class 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

9498
def 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

112116
def 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

183190
class 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

296305
class 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):
397421
class 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

598657
class 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

Comments
 (0)