First public commit

This commit is contained in:
ben
2018-09-18 10:52:38 +02:00
commit f57654b84b
58 changed files with 9728 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
from .pdf import PdfFileReader, PdfFileWriter
from .merger import PdfFileMerger
from .pagerange import PageRange, parse_filename_page_ranges
from ._version import __version__
__all__ = ["pdf", "PdfFileMerger"]

View File

@@ -0,0 +1 @@
__version__ = '1.26.0'

View File

@@ -0,0 +1,424 @@
# vim: sw=4:expandtab:foldmethod=marker
#
# Copyright (c) 2006, Mathieu Fenniak
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Implementation of stream filters for PDF.
"""
__author__ = "Mathieu Fenniak"
__author_email__ = "biziqe@mathieu.fenniak.net"
import math
from .utils import PdfReadError, ord_, chr_, paethPredictor
from sys import version_info
if version_info < ( 3, 0 ):
from cStringIO import StringIO
else:
from io import StringIO
import struct
try:
import zlib
def decompress(data):
return zlib.decompress(data)
def compress(data):
return zlib.compress(data)
except ImportError:
# Unable to import zlib. Attempt to use the System.IO.Compression
# library from the .NET framework. (IronPython only)
import System
from System import IO, Collections, Array
def _string_to_bytearr(buf):
retval = Array.CreateInstance(System.Byte, len(buf))
for i in range(len(buf)):
retval[i] = ord(buf[i])
return retval
def _bytearr_to_string(bytes):
retval = ""
for i in range(bytes.Length):
retval += chr(bytes[i])
return retval
def _read_bytes(stream):
ms = IO.MemoryStream()
buf = Array.CreateInstance(System.Byte, 2048)
while True:
bytes = stream.Read(buf, 0, buf.Length)
if bytes == 0:
break
else:
ms.Write(buf, 0, bytes)
retval = ms.ToArray()
ms.Close()
return retval
def decompress(data):
bytes = _string_to_bytearr(data)
ms = IO.MemoryStream()
ms.Write(bytes, 0, bytes.Length)
ms.Position = 0 # fseek 0
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Decompress)
bytes = _read_bytes(gz)
retval = _bytearr_to_string(bytes)
gz.Close()
return retval
def compress(data):
bytes = _string_to_bytearr(data)
ms = IO.MemoryStream()
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Compress, True)
gz.Write(bytes, 0, bytes.Length)
gz.Close()
ms.Position = 0 # fseek 0
bytes = ms.ToArray()
retval = _bytearr_to_string(bytes)
ms.Close()
return retval
class FlateDecode(object):
def decode(data, decodeParms):
data = decompress(data)
predictor = 1
if decodeParms:
try:
predictor = decodeParms.get("/Predictor", 1)
except AttributeError:
pass # usually an array with a null object was read
# predictor 1 == no predictor
if predictor != 1:
columns = decodeParms["/Columns"]
# PNG prediction:
if predictor >= 10 and predictor <= 15:
output = StringIO()
# PNG prediction can vary from row to row
rowlength = columns + 1
assert len(data) % rowlength == 0
prev_rowdata = (0,) * rowlength
for row in range(len(data) // rowlength):
rowdata = [ord_(x) for x in data[(row*rowlength):((row+1)*rowlength)]]
filterByte = rowdata[0]
if filterByte == 0:
pass
elif filterByte == 1:
for i in range(2, rowlength):
rowdata[i] = (rowdata[i] + rowdata[i-1]) % 256
elif filterByte == 2:
for i in range(1, rowlength):
rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256
elif filterByte == 3:
for i in range(1, rowlength):
left = rowdata[i-1] if i > 1 else 0
floor = math.floor(left + prev_rowdata[i])/2
rowdata[i] = (rowdata[i] + int(floor)) % 256
elif filterByte == 4:
for i in range(1, rowlength):
left = rowdata[i - 1] if i > 1 else 0
up = prev_rowdata[i]
up_left = prev_rowdata[i - 1] if i > 1 else 0
paeth = paethPredictor(left, up, up_left)
rowdata[i] = (rowdata[i] + paeth) % 256
else:
# unsupported PNG filter
raise PdfReadError("Unsupported PNG filter %r" % filterByte)
prev_rowdata = rowdata
output.write(''.join([chr(x) for x in rowdata[1:]]))
data = output.getvalue()
else:
# unsupported predictor
raise PdfReadError("Unsupported flatedecode predictor %r" % predictor)
return data
decode = staticmethod(decode)
def encode(data):
return compress(data)
encode = staticmethod(encode)
class ASCIIHexDecode(object):
def decode(data, decodeParms=None):
retval = ""
char = ""
x = 0
while True:
c = data[x]
if c == ">":
break
elif c.isspace():
x += 1
continue
char += c
if len(char) == 2:
retval += chr(int(char, base=16))
char = ""
x += 1
assert char == ""
return retval
decode = staticmethod(decode)
class LZWDecode(object):
"""Taken from:
http://www.java2s.com/Open-Source/Java-Document/PDF/PDF-Renderer/com/sun/pdfview/decode/LZWDecode.java.htm
"""
class decoder(object):
def __init__(self, data):
self.STOP=257
self.CLEARDICT=256
self.data=data
self.bytepos=0
self.bitpos=0
self.dict=[""]*4096
for i in range(256):
self.dict[i]=chr(i)
self.resetDict()
def resetDict(self):
self.dictlen=258
self.bitspercode=9
def nextCode(self):
fillbits=self.bitspercode
value=0
while fillbits>0 :
if self.bytepos >= len(self.data):
return -1
nextbits=ord_(self.data[self.bytepos])
bitsfromhere=8-self.bitpos
if bitsfromhere>fillbits:
bitsfromhere=fillbits
value |= (((nextbits >> (8-self.bitpos-bitsfromhere)) &
(0xff >> (8-bitsfromhere))) <<
(fillbits-bitsfromhere))
fillbits -= bitsfromhere
self.bitpos += bitsfromhere
if self.bitpos >=8:
self.bitpos=0
self.bytepos = self.bytepos+1
return value
def decode(self):
""" algorithm derived from:
http://www.rasip.fer.hr/research/compress/algorithms/fund/lz/lzw.html
and the PDFReference
"""
cW = self.CLEARDICT;
baos=""
while True:
pW = cW;
cW = self.nextCode();
if cW == -1:
raise PdfReadError("Missed the stop code in LZWDecode!")
if cW == self.STOP:
break;
elif cW == self.CLEARDICT:
self.resetDict();
elif pW == self.CLEARDICT:
baos+=self.dict[cW]
else:
if cW < self.dictlen:
baos += self.dict[cW]
p=self.dict[pW]+self.dict[cW][0]
self.dict[self.dictlen]=p
self.dictlen+=1
else:
p=self.dict[pW]+self.dict[pW][0]
baos+=p
self.dict[self.dictlen] = p;
self.dictlen+=1
if (self.dictlen >= (1 << self.bitspercode) - 1 and
self.bitspercode < 12):
self.bitspercode+=1
return baos
@staticmethod
def decode(data,decodeParams=None):
return LZWDecode.decoder(data).decode()
class ASCII85Decode(object):
def decode(data, decodeParms=None):
if version_info < ( 3, 0 ):
retval = ""
group = []
x = 0
hitEod = False
# remove all whitespace from data
data = [y for y in data if not (y in ' \n\r\t')]
while not hitEod:
c = data[x]
if len(retval) == 0 and c == "<" and data[x+1] == "~":
x += 2
continue
#elif c.isspace():
# x += 1
# continue
elif c == 'z':
assert len(group) == 0
retval += '\x00\x00\x00\x00'
x += 1
continue
elif c == "~" and data[x+1] == ">":
if len(group) != 0:
# cannot have a final group of just 1 char
assert len(group) > 1
cnt = len(group) - 1
group += [ 85, 85, 85 ]
hitEod = cnt
else:
break
else:
c = ord(c) - 33
assert c >= 0 and c < 85
group += [ c ]
if len(group) >= 5:
b = group[0] * (85**4) + \
group[1] * (85**3) + \
group[2] * (85**2) + \
group[3] * 85 + \
group[4]
assert b < (2**32 - 1)
c4 = chr((b >> 0) % 256)
c3 = chr((b >> 8) % 256)
c2 = chr((b >> 16) % 256)
c1 = chr(b >> 24)
retval += (c1 + c2 + c3 + c4)
if hitEod:
retval = retval[:-4+hitEod]
group = []
x += 1
return retval
else:
if isinstance(data, str):
data = data.encode('ascii')
n = b = 0
out = bytearray()
for c in data:
if ord('!') <= c and c <= ord('u'):
n += 1
b = b*85+(c-33)
if n == 5:
out += struct.pack(b'>L',b)
n = b = 0
elif c == ord('z'):
assert n == 0
out += b'\0\0\0\0'
elif c == ord('~'):
if n:
for _ in range(5-n):
b = b*85+84
out += struct.pack(b'>L',b)[:n-1]
break
return bytes(out)
decode = staticmethod(decode)
class DCTDecode(object):
def decode(data, decodeParms=None):
return data
decode = staticmethod(decode)
class JPXDecode(object):
def decode(data, decodeParms=None):
return data
decode = staticmethod(decode)
class CCITTFaxDecode(object):
def decode(data, decodeParms=None, height=0):
if decodeParms:
if decodeParms.get("/K", 1) == -1:
CCITTgroup = 4
else:
CCITTgroup = 3
width = decodeParms["/Columns"]
imgSize = len(data)
tiff_header_struct = '<' + '2s' + 'h' + 'l' + 'h' + 'hhll' * 8 + 'h'
tiffHeader = struct.pack(tiff_header_struct,
b'II', # Byte order indication: Little endian
42, # Version number (always 42)
8, # Offset to first IFD
8, # Number of tags in IFD
256, 4, 1, width, # ImageWidth, LONG, 1, width
257, 4, 1, height, # ImageLength, LONG, 1, length
258, 3, 1, 1, # BitsPerSample, SHORT, 1, 1
259, 3, 1, CCITTgroup, # Compression, SHORT, 1, 4 = CCITT Group 4 fax encoding
262, 3, 1, 0, # Thresholding, SHORT, 1, 0 = WhiteIsZero
273, 4, 1, struct.calcsize(tiff_header_struct), # StripOffsets, LONG, 1, length of header
278, 4, 1, height, # RowsPerStrip, LONG, 1, length
279, 4, 1, imgSize, # StripByteCounts, LONG, 1, size of image
0 # last IFD
)
return tiffHeader + data
decode = staticmethod(decode)
def decodeStreamData(stream):
from .generic import NameObject
filters = stream.get("/Filter", ())
if len(filters) and not isinstance(filters[0], NameObject):
# we have a single filter instance
filters = (filters,)
data = stream._data
# If there is not data to decode we should not try to decode the data.
if data:
for filterType in filters:
if filterType == "/FlateDecode" or filterType == "/Fl":
data = FlateDecode.decode(data, stream.get("/DecodeParms"))
elif filterType == "/ASCIIHexDecode" or filterType == "/AHx":
data = ASCIIHexDecode.decode(data)
elif filterType == "/LZWDecode" or filterType == "/LZW":
data = LZWDecode.decode(data, stream.get("/DecodeParms"))
elif filterType == "/ASCII85Decode" or filterType == "/A85":
data = ASCII85Decode.decode(data)
elif filterType == "/DCTDecode":
data = DCTDecode.decode(data)
elif filterType == "/JPXDecode":
data = JPXDecode.decode(data)
elif filterType == "/CCITTFaxDecode":
height = stream.get("/Height", ())
data = CCITTFaxDecode.decode(data, stream.get("/DecodeParms"), height)
elif filterType == "/Crypt":
decodeParams = stream.get("/DecodeParams", {})
if "/Name" not in decodeParams and "/Type" not in decodeParams:
pass
else:
raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet")
else:
# unsupported filter
raise NotImplementedError("unsupported filter %s" % filterType)
return data

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,553 @@
# vim: sw=4:expandtab:foldmethod=marker
#
# Copyright (c) 2006, Mathieu Fenniak
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from .generic import *
from .utils import isString, str_
from .pdf import PdfFileReader, PdfFileWriter
from .pagerange import PageRange
from sys import version_info
if version_info < ( 3, 0 ):
from cStringIO import StringIO
StreamIO = StringIO
else:
from io import BytesIO
from io import FileIO as file
StreamIO = BytesIO
class _MergedPage(object):
"""
_MergedPage is used internally by PdfFileMerger to collect necessary
information on each page that is being merged.
"""
def __init__(self, pagedata, src, id):
self.src = src
self.pagedata = pagedata
self.out_pagedata = None
self.id = id
class PdfFileMerger(object):
"""
Initializes a PdfFileMerger object. PdfFileMerger merges multiple PDFs
into a single PDF. It can concatenate, slice, insert, or any combination
of the above.
See the functions :meth:`merge()<merge>` (or :meth:`append()<append>`)
and :meth:`write()<write>` for usage information.
:param bool strict: Determines whether user should be warned of all
problems and also causes some correctable problems to be fatal.
Defaults to ``True``.
"""
def __init__(self, strict=True):
self.inputs = []
self.pages = []
self.output = PdfFileWriter()
self.bookmarks = []
self.named_dests = []
self.id_count = 0
self.strict = strict
def merge(self, position, fileobj, bookmark=None, pages=None, import_bookmarks=True):
"""
Merges the pages from the given file into the output file at the
specified page number.
:param int position: The *page number* to insert this file. File will
be inserted after the given number.
:param fileobj: A File Object or an object that supports the standard read
and seek methods similar to a File Object. Could also be a
string representing a path to a PDF file.
:param str bookmark: Optionally, you may specify a bookmark to be applied at
the beginning of the included file by supplying the text of the bookmark.
:param pages: can be a :ref:`Page Range <page-range>` or a ``(start, stop[, step])`` tuple
to merge only the specified range of pages from the source
document into the output document.
:param bool import_bookmarks: You may prevent the source document's bookmarks
from being imported by specifying this as ``False``.
"""
# This parameter is passed to self.inputs.append and means
# that the stream used was created in this method.
my_file = False
# If the fileobj parameter is a string, assume it is a path
# and create a file object at that location. If it is a file,
# copy the file's contents into a BytesIO (or StreamIO) stream object; if
# it is a PdfFileReader, copy that reader's stream into a
# BytesIO (or StreamIO) stream.
# If fileobj is none of the above types, it is not modified
decryption_key = None
if isString(fileobj):
fileobj = file(fileobj, 'rb')
my_file = True
elif hasattr(fileobj, "seek") and hasattr(fileobj, "read"):
fileobj.seek(0)
filecontent = fileobj.read()
fileobj = StreamIO(filecontent)
my_file = True
elif isinstance(fileobj, PdfFileReader):
orig_tell = fileobj.stream.tell()
fileobj.stream.seek(0)
filecontent = StreamIO(fileobj.stream.read())
fileobj.stream.seek(orig_tell) # reset the stream to its original location
fileobj = filecontent
if hasattr(fileobj, '_decryption_key'):
decryption_key = fileobj._decryption_key
my_file = True
# Create a new PdfFileReader instance using the stream
# (either file or BytesIO or StringIO) created above
pdfr = PdfFileReader(fileobj, strict=self.strict)
if decryption_key is not None:
pdfr._decryption_key = decryption_key
# Find the range of pages to merge.
if pages == None:
pages = (0, pdfr.getNumPages())
elif isinstance(pages, PageRange):
pages = pages.indices(pdfr.getNumPages())
elif not isinstance(pages, tuple):
raise TypeError('"pages" must be a tuple of (start, stop[, step])')
srcpages = []
if bookmark:
bookmark = Bookmark(TextStringObject(bookmark), NumberObject(self.id_count), NameObject('/Fit'))
outline = []
if import_bookmarks:
outline = pdfr.getOutlines()
outline = self._trim_outline(pdfr, outline, pages)
if bookmark:
self.bookmarks += [bookmark, outline]
else:
self.bookmarks += outline
dests = pdfr.namedDestinations
dests = self._trim_dests(pdfr, dests, pages)
self.named_dests += dests
# Gather all the pages that are going to be merged
for i in range(*pages):
pg = pdfr.getPage(i)
id = self.id_count
self.id_count += 1
mp = _MergedPage(pg, pdfr, id)
srcpages.append(mp)
self._associate_dests_to_pages(srcpages)
self._associate_bookmarks_to_pages(srcpages)
# Slice to insert the pages at the specified position
self.pages[position:position] = srcpages
# Keep track of our input files so we can close them later
self.inputs.append((fileobj, pdfr, my_file))
def append(self, fileobj, bookmark=None, pages=None, import_bookmarks=True):
"""
Identical to the :meth:`merge()<merge>` method, but assumes you want to concatenate
all pages onto the end of the file instead of specifying a position.
:param fileobj: A File Object or an object that supports the standard read
and seek methods similar to a File Object. Could also be a
string representing a path to a PDF file.
:param str bookmark: Optionally, you may specify a bookmark to be applied at
the beginning of the included file by supplying the text of the bookmark.
:param pages: can be a :ref:`Page Range <page-range>` or a ``(start, stop[, step])`` tuple
to merge only the specified range of pages from the source
document into the output document.
:param bool import_bookmarks: You may prevent the source document's bookmarks
from being imported by specifying this as ``False``.
"""
self.merge(len(self.pages), fileobj, bookmark, pages, import_bookmarks)
def write(self, fileobj):
"""
Writes all data that has been merged to the given output file.
:param fileobj: Output file. Can be a filename or any kind of
file-like object.
"""
my_file = False
if isString(fileobj):
fileobj = file(fileobj, 'wb')
my_file = True
# Add pages to the PdfFileWriter
# The commented out line below was replaced with the two lines below it to allow PdfFileMerger to work with PyPdf 1.13
for page in self.pages:
self.output.addPage(page.pagedata)
page.out_pagedata = self.output.getReference(self.output._pages.getObject()["/Kids"][-1].getObject())
#idnum = self.output._objects.index(self.output._pages.getObject()["/Kids"][-1].getObject()) + 1
#page.out_pagedata = IndirectObject(idnum, 0, self.output)
# Once all pages are added, create bookmarks to point at those pages
self._write_dests()
self._write_bookmarks()
# Write the output to the file
self.output.write(fileobj)
if my_file:
fileobj.close()
def close(self):
"""
Shuts all file descriptors (input and output) and clears all memory
usage.
"""
self.pages = []
for fo, pdfr, mine in self.inputs:
if mine:
fo.close()
self.inputs = []
self.output = None
def addMetadata(self, infos):
"""
Add custom metadata to the output.
:param dict infos: a Python dictionary where each key is a field
and each value is your new metadata.
Example: ``{u'/Title': u'My title'}``
"""
self.output.addMetadata(infos)
def setPageLayout(self, layout):
"""
Set the page layout
:param str layout: The page layout to be used
Valid layouts are:
/NoLayout Layout explicitly not specified
/SinglePage Show one page at a time
/OneColumn Show one column at a time
/TwoColumnLeft Show pages in two columns, odd-numbered pages on the left
/TwoColumnRight Show pages in two columns, odd-numbered pages on the right
/TwoPageLeft Show two pages at a time, odd-numbered pages on the left
/TwoPageRight Show two pages at a time, odd-numbered pages on the right
"""
self.output.setPageLayout(layout)
def setPageMode(self, mode):
"""
Set the page mode.
:param str mode: The page mode to use.
Valid modes are:
/UseNone Do not show outlines or thumbnails panels
/UseOutlines Show outlines (aka bookmarks) panel
/UseThumbs Show page thumbnails panel
/FullScreen Fullscreen view
/UseOC Show Optional Content Group (OCG) panel
/UseAttachments Show attachments panel
"""
self.output.setPageMode(mode)
def _trim_dests(self, pdf, dests, pages):
"""
Removes any named destinations that are not a part of the specified
page set.
"""
new_dests = []
prev_header_added = True
for k, o in list(dests.items()):
for j in range(*pages):
if pdf.getPage(j).getObject() == o['/Page'].getObject():
o[NameObject('/Page')] = o['/Page'].getObject()
assert str_(k) == str_(o['/Title'])
new_dests.append(o)
break
return new_dests
def _trim_outline(self, pdf, outline, pages):
"""
Removes any outline/bookmark entries that are not a part of the
specified page set.
"""
new_outline = []
prev_header_added = True
for i, o in enumerate(outline):
if isinstance(o, list):
sub = self._trim_outline(pdf, o, pages)
if sub:
if not prev_header_added:
new_outline.append(outline[i-1])
new_outline.append(sub)
else:
prev_header_added = False
for j in range(*pages):
if pdf.getPage(j).getObject() == o['/Page'].getObject():
o[NameObject('/Page')] = o['/Page'].getObject()
new_outline.append(o)
prev_header_added = True
break
return new_outline
def _write_dests(self):
dests = self.named_dests
for v in dests:
pageno = None
pdf = None
if '/Page' in v:
for i, p in enumerate(self.pages):
if p.id == v['/Page']:
v[NameObject('/Page')] = p.out_pagedata
pageno = i
pdf = p.src
break
if pageno != None:
self.output.addNamedDestinationObject(v)
def _write_bookmarks(self, bookmarks=None, parent=None):
if bookmarks == None:
bookmarks = self.bookmarks
last_added = None
for b in bookmarks:
if isinstance(b, list):
self._write_bookmarks(b, last_added)
continue
pageno = None
pdf = None
if '/Page' in b:
for i, p in enumerate(self.pages):
if p.id == b['/Page']:
#b[NameObject('/Page')] = p.out_pagedata
args = [NumberObject(p.id), NameObject(b['/Type'])]
#nothing more to add
#if b['/Type'] == '/Fit' or b['/Type'] == '/FitB'
if b['/Type'] == '/FitH' or b['/Type'] == '/FitBH':
if '/Top' in b and not isinstance(b['/Top'], NullObject):
args.append(FloatObject(b['/Top']))
else:
args.append(FloatObject(0))
del b['/Top']
elif b['/Type'] == '/FitV' or b['/Type'] == '/FitBV':
if '/Left' in b and not isinstance(b['/Left'], NullObject):
args.append(FloatObject(b['/Left']))
else:
args.append(FloatObject(0))
del b['/Left']
elif b['/Type'] == '/XYZ':
if '/Left' in b and not isinstance(b['/Left'], NullObject):
args.append(FloatObject(b['/Left']))
else:
args.append(FloatObject(0))
if '/Top' in b and not isinstance(b['/Top'], NullObject):
args.append(FloatObject(b['/Top']))
else:
args.append(FloatObject(0))
if '/Zoom' in b and not isinstance(b['/Zoom'], NullObject):
args.append(FloatObject(b['/Zoom']))
else:
args.append(FloatObject(0))
del b['/Top'], b['/Zoom'], b['/Left']
elif b['/Type'] == '/FitR':
if '/Left' in b and not isinstance(b['/Left'], NullObject):
args.append(FloatObject(b['/Left']))
else:
args.append(FloatObject(0))
if '/Bottom' in b and not isinstance(b['/Bottom'], NullObject):
args.append(FloatObject(b['/Bottom']))
else:
args.append(FloatObject(0))
if '/Right' in b and not isinstance(b['/Right'], NullObject):
args.append(FloatObject(b['/Right']))
else:
args.append(FloatObject(0))
if '/Top' in b and not isinstance(b['/Top'], NullObject):
args.append(FloatObject(b['/Top']))
else:
args.append(FloatObject(0))
del b['/Left'], b['/Right'], b['/Bottom'], b['/Top']
b[NameObject('/A')] = DictionaryObject({NameObject('/S'): NameObject('/GoTo'), NameObject('/D'): ArrayObject(args)})
pageno = i
pdf = p.src
break
if pageno != None:
del b['/Page'], b['/Type']
last_added = self.output.addBookmarkDict(b, parent)
def _associate_dests_to_pages(self, pages):
for nd in self.named_dests:
pageno = None
np = nd['/Page']
if isinstance(np, NumberObject):
continue
for p in pages:
if np.getObject() == p.pagedata.getObject():
pageno = p.id
if pageno != None:
nd[NameObject('/Page')] = NumberObject(pageno)
else:
raise ValueError("Unresolved named destination '%s'" % (nd['/Title'],))
def _associate_bookmarks_to_pages(self, pages, bookmarks=None):
if bookmarks == None:
bookmarks = self.bookmarks
for b in bookmarks:
if isinstance(b, list):
self._associate_bookmarks_to_pages(pages, b)
continue
pageno = None
bp = b['/Page']
if isinstance(bp, NumberObject):
continue
for p in pages:
if bp.getObject() == p.pagedata.getObject():
pageno = p.id
if pageno != None:
b[NameObject('/Page')] = NumberObject(pageno)
else:
raise ValueError("Unresolved bookmark '%s'" % (b['/Title'],))
def findBookmark(self, bookmark, root=None):
if root == None:
root = self.bookmarks
for i, b in enumerate(root):
if isinstance(b, list):
res = self.findBookmark(bookmark, b)
if res:
return [i] + res
elif b == bookmark or b['/Title'] == bookmark:
return [i]
return None
def addBookmark(self, title, pagenum, parent=None):
"""
Add a bookmark to this PDF file.
:param str title: Title to use for this bookmark.
:param int pagenum: Page number this bookmark will point to.
:param parent: A reference to a parent bookmark to create nested
bookmarks.
"""
if parent == None:
iloc = [len(self.bookmarks)-1]
elif isinstance(parent, list):
iloc = parent
else:
iloc = self.findBookmark(parent)
dest = Bookmark(TextStringObject(title), NumberObject(pagenum), NameObject('/FitH'), NumberObject(826))
if parent == None:
self.bookmarks.append(dest)
else:
bmparent = self.bookmarks
for i in iloc[:-1]:
bmparent = bmparent[i]
npos = iloc[-1]+1
if npos < len(bmparent) and isinstance(bmparent[npos], list):
bmparent[npos].append(dest)
else:
bmparent.insert(npos, [dest])
return dest
def addNamedDestination(self, title, pagenum):
"""
Add a destination to the output.
:param str title: Title to use
:param int pagenum: Page number this destination points at.
"""
dest = Destination(TextStringObject(title), NumberObject(pagenum), NameObject('/FitH'), NumberObject(826))
self.named_dests.append(dest)
class OutlinesObject(list):
def __init__(self, pdf, tree, parent=None):
list.__init__(self)
self.tree = tree
self.pdf = pdf
self.parent = parent
def remove(self, index):
obj = self[index]
del self[index]
self.tree.removeChild(obj)
def add(self, title, pagenum):
pageRef = self.pdf.getObject(self.pdf._pages)['/Kids'][pagenum]
action = DictionaryObject()
action.update({
NameObject('/D') : ArrayObject([pageRef, NameObject('/FitH'), NumberObject(826)]),
NameObject('/S') : NameObject('/GoTo')
})
actionRef = self.pdf._addObject(action)
bookmark = TreeObject()
bookmark.update({
NameObject('/A'): actionRef,
NameObject('/Title'): createStringObject(title),
})
self.pdf._addObject(bookmark)
self.tree.addChild(bookmark)
def removeAll(self):
for child in [x for x in self.tree.children()]:
self.tree.removeChild(child)
self.pop()

View File

@@ -0,0 +1,152 @@
#!/usr/bin/env python
"""
Representation and utils for ranges of PDF file pages.
Copyright (c) 2014, Steve Witham <switham_github@mac-guyver.com>.
All rights reserved. This software is available under a BSD license;
see https://github.com/mstamy2/PyPDF2/blob/master/LICENSE
"""
import re
from .utils import isString
_INT_RE = r"(0|-?[1-9]\d*)" # A decimal int, don't allow "-0".
PAGE_RANGE_RE = "^({int}|({int}?(:{int}?(:{int}?)?)))$".format(int=_INT_RE)
# groups: 12 34 5 6 7 8
class ParseError(Exception):
pass
PAGE_RANGE_HELP = """Remember, page indices start with zero.
Page range expression examples:
: all pages. -1 last page.
22 just the 23rd page. :-1 all but the last page.
0:3 the first three pages. -2 second-to-last page.
:3 the first three pages. -2: last two pages.
5: from the sixth page onward. -3:-1 third & second to last.
The third, "stride" or "step" number is also recognized.
::2 0 2 4 ... to the end. 3:0:-1 3 2 1 but not 0.
1:10:2 1 3 5 7 9 2::-1 2 1 0.
::-1 all pages in reverse order.
"""
class PageRange(object):
"""
A slice-like representation of a range of page indices,
i.e. page numbers, only starting at zero.
The syntax is like what you would put between brackets [ ].
The slice is one of the few Python types that can't be subclassed,
but this class converts to and from slices, and allows similar use.
o PageRange(str) parses a string representing a page range.
o PageRange(slice) directly "imports" a slice.
o to_slice() gives the equivalent slice.
o str() and repr() allow printing.
o indices(n) is like slice.indices(n).
"""
def __init__(self, arg):
"""
Initialize with either a slice -- giving the equivalent page range,
or a PageRange object -- making a copy,
or a string like
"int", "[int]:[int]" or "[int]:[int]:[int]",
where the brackets indicate optional ints.
{page_range_help}
Note the difference between this notation and arguments to slice():
slice(3) means the first three pages;
PageRange("3") means the range of only the fourth page.
However PageRange(slice(3)) means the first three pages.
"""
if isinstance(arg, slice):
self._slice = arg
return
if isinstance(arg, PageRange):
self._slice = arg.to_slice()
return
m = isString(arg) and re.match(PAGE_RANGE_RE, arg)
if not m:
raise ParseError(arg)
elif m.group(2):
# Special case: just an int means a range of one page.
start = int(m.group(2))
stop = start + 1 if start != -1 else None
self._slice = slice(start, stop)
else:
self._slice = slice(*[int(g) if g else None
for g in m.group(4, 6, 8)])
# Just formatting this when there is __doc__ for __init__
if __init__.__doc__:
__init__.__doc__ = __init__.__doc__.format(page_range_help=PAGE_RANGE_HELP)
@staticmethod
def valid(input):
""" True if input is a valid initializer for a PageRange. """
return isinstance(input, slice) or \
isinstance(input, PageRange) or \
(isString(input)
and bool(re.match(PAGE_RANGE_RE, input)))
def to_slice(self):
""" Return the slice equivalent of this page range. """
return self._slice
def __str__(self):
""" A string like "1:2:3". """
s = self._slice
if s.step == None:
if s.start != None and s.stop == s.start + 1:
return str(s.start)
indices = s.start, s.stop
else:
indices = s.start, s.stop, s.step
return ':'.join("" if i == None else str(i) for i in indices)
def __repr__(self):
""" A string like "PageRange('1:2:3')". """
return "PageRange(" + repr(str(self)) + ")"
def indices(self, n):
"""
n is the length of the list of pages to choose from.
Returns arguments for range(). See help(slice.indices).
"""
return self._slice.indices(n)
PAGE_RANGE_ALL = PageRange(":") # The range of all pages.
def parse_filename_page_ranges(args):
"""
Given a list of filenames and page ranges, return a list of
(filename, page_range) pairs.
First arg must be a filename; other ags are filenames, page-range
expressions, slice objects, or PageRange objects.
A filename not followed by a page range indicates all pages of the file.
"""
pairs = []
pdf_filename = None
did_page_range = False
for arg in args + [None]:
if PageRange.valid(arg):
if not pdf_filename:
raise ValueError("The first argument must be a filename, " \
"not a page range.")
pairs.append( (pdf_filename, PageRange(arg)) )
did_page_range = True
else:
# New filename or end of list--do all of the previous file?
if pdf_filename and not did_page_range:
pairs.append( (pdf_filename, PAGE_RANGE_ALL) )
pdf_filename = arg
did_page_range = False
return pairs

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,309 @@
# Copyright (c) 2006, Mathieu Fenniak
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Utility functions for PDF library.
"""
__author__ = "Mathieu Fenniak"
__author_email__ = "biziqe@mathieu.fenniak.net"
import sys
try:
import __builtin__ as builtins
except ImportError: # Py3
import builtins
xrange_fn = getattr(builtins, "xrange", range)
_basestring = getattr(builtins, "basestring", str)
bytes_type = type(bytes()) # Works the same in Python 2.X and 3.X
string_type = getattr(builtins, "unicode", str)
int_types = (int, long) if sys.version_info[0] < 3 else (int,)
# Make basic type tests more consistent
def isString(s):
"""Test if arg is a string. Compatible with Python 2 and 3."""
return isinstance(s, _basestring)
def isInt(n):
"""Test if arg is an int. Compatible with Python 2 and 3."""
return isinstance(n, int_types)
def isBytes(b):
"""Test if arg is a bytes instance. Compatible with Python 2 and 3."""
return isinstance(b, bytes_type)
#custom implementation of warnings.formatwarning
def formatWarning(message, category, filename, lineno, line=None):
file = filename.replace("/", "\\").rsplit("\\", 1)[1] # find the file name
return "%s: %s [%s:%s]\n" % (category.__name__, message, file, lineno)
def readUntilWhitespace(stream, maxchars=None):
"""
Reads non-whitespace characters and returns them.
Stops upon encountering whitespace or when maxchars is reached.
"""
txt = b_("")
while True:
tok = stream.read(1)
if tok.isspace() or not tok:
break
txt += tok
if len(txt) == maxchars:
break
return txt
def readNonWhitespace(stream):
"""
Finds and reads the next non-whitespace character (ignores whitespace).
"""
tok = WHITESPACES[0]
while tok in WHITESPACES:
tok = stream.read(1)
return tok
def skipOverWhitespace(stream):
"""
Similar to readNonWhitespace, but returns a Boolean if more than
one whitespace character was read.
"""
tok = WHITESPACES[0]
cnt = 0;
while tok in WHITESPACES:
tok = stream.read(1)
cnt+=1
return (cnt > 1)
def skipOverComment(stream):
tok = stream.read(1)
stream.seek(-1, 1)
if tok == b_('%'):
while tok not in (b_('\n'), b_('\r')):
tok = stream.read(1)
def readUntilRegex(stream, regex, ignore_eof=False):
"""
Reads until the regular expression pattern matched (ignore the match)
Raise PdfStreamError on premature end-of-file.
:param bool ignore_eof: If true, ignore end-of-line and return immediately
"""
name = b_('')
while True:
tok = stream.read(16)
if not tok:
# stream has truncated prematurely
if ignore_eof == True:
return name
else:
raise PdfStreamError("Stream has ended unexpectedly")
m = regex.search(tok)
if m is not None:
name += tok[:m.start()]
stream.seek(m.start()-len(tok), 1)
break
name += tok
return name
class ConvertFunctionsToVirtualList(object):
def __init__(self, lengthFunction, getFunction):
self.lengthFunction = lengthFunction
self.getFunction = getFunction
def __len__(self):
return self.lengthFunction()
def __getitem__(self, index):
if isinstance(index, slice):
indices = xrange_fn(*index.indices(len(self)))
cls = type(self)
return cls(indices.__len__, lambda idx: self[indices[idx]])
if not isInt(index):
raise TypeError("sequence indices must be integers")
len_self = len(self)
if index < 0:
# support negative indexes
index = len_self + index
if index < 0 or index >= len_self:
raise IndexError("sequence index out of range")
return self.getFunction(index)
def RC4_encrypt(key, plaintext):
S = [i for i in range(256)]
j = 0
for i in range(256):
j = (j + S[i] + ord_(key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i]
i, j = 0, 0
retval = []
for x in range(len(plaintext)):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = S[(S[i] + S[j]) % 256]
retval.append(b_(chr(ord_(plaintext[x]) ^ t)))
return b_("").join(retval)
def matrixMultiply(a, b):
return [[sum([float(i)*float(j)
for i, j in zip(row, col)]
) for col in zip(*b)]
for row in a]
def markLocation(stream):
"""Creates text file showing current location in context."""
# Mainly for debugging
RADIUS = 5000
stream.seek(-RADIUS, 1)
outputDoc = open('PyPDF2_pdfLocation.txt', 'w')
outputDoc.write(stream.read(RADIUS))
outputDoc.write('HERE')
outputDoc.write(stream.read(RADIUS))
outputDoc.close()
stream.seek(-RADIUS, 1)
class PyPdfError(Exception):
pass
class PdfReadError(PyPdfError):
pass
class PageSizeNotDefinedError(PyPdfError):
pass
class PdfReadWarning(UserWarning):
pass
class PdfStreamError(PdfReadError):
pass
if sys.version_info[0] < 3:
def b_(s):
return s
else:
B_CACHE = {}
def b_(s):
bc = B_CACHE
if s in bc:
return bc[s]
if type(s) == bytes:
return s
else:
r = s.encode('latin-1')
if len(s) < 2:
bc[s] = r
return r
def u_(s):
if sys.version_info[0] < 3:
return unicode(s, 'unicode_escape')
else:
return s
def str_(b):
if sys.version_info[0] < 3:
return b
else:
if type(b) == bytes:
return b.decode('latin-1')
else:
return b
def ord_(b):
if sys.version_info[0] < 3 or type(b) == str:
return ord(b)
else:
return b
def chr_(c):
if sys.version_info[0] < 3:
return c
else:
return chr(c)
def barray(b):
if sys.version_info[0] < 3:
return b
else:
return bytearray(b)
def hexencode(b):
if sys.version_info[0] < 3:
return b.encode('hex')
else:
import codecs
coder = codecs.getencoder('hex_codec')
return coder(b)[0]
def hexStr(num):
return hex(num).replace('L', '')
WHITESPACES = [b_(x) for x in [' ', '\n', '\r', '\t', '\x00']]
def paethPredictor(left, up, up_left):
p = left + up - up_left
dist_left = abs(p - left)
dist_up = abs(p - up)
dist_up_left = abs(p - up_left)
if dist_left <= dist_up and dist_left <= dist_up_left:
return left
elif dist_up <= dist_up_left:
return up
else:
return up_left

View File

@@ -0,0 +1,358 @@
import re
import datetime
import decimal
from .generic import PdfObject
from xml.dom import getDOMImplementation
from xml.dom.minidom import parseString
from .utils import u_
RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
DC_NAMESPACE = "http://purl.org/dc/elements/1.1/"
XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/"
PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/"
XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/"
# What is the PDFX namespace, you might ask? I might ask that too. It's
# a completely undocumented namespace used to place "custom metadata"
# properties, which are arbitrary metadata properties with no semantic or
# documented meaning. Elements in the namespace are key/value-style storage,
# where the element name is the key and the content is the value. The keys
# are transformed into valid XML identifiers by substituting an invalid
# identifier character with \u2182 followed by the unicode hex ID of the
# original character. A key like "my car" is therefore "my\u21820020car".
#
# \u2182, in case you're wondering, is the unicode character
# \u{ROMAN NUMERAL TEN THOUSAND}, a straightforward and obvious choice for
# escaping characters.
#
# Intentional users of the pdfx namespace should be shot on sight. A
# custom data schema and sensical XML elements could be used instead, as is
# suggested by Adobe's own documentation on XMP (under "Extensibility of
# Schemas").
#
# Information presented here on the /pdfx/ schema is a result of limited
# reverse engineering, and does not constitute a full specification.
PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/"
iso8601 = re.compile("""
(?P<year>[0-9]{4})
(-
(?P<month>[0-9]{2})
(-
(?P<day>[0-9]+)
(T
(?P<hour>[0-9]{2}):
(?P<minute>[0-9]{2})
(:(?P<second>[0-9]{2}(.[0-9]+)?))?
(?P<tzd>Z|[-+][0-9]{2}:[0-9]{2})
)?
)?
)?
""", re.VERBOSE)
class XmpInformation(PdfObject):
"""
An object that represents Adobe XMP metadata.
Usually accessed by :meth:`getXmpMetadata()<PyPDF2.PdfFileReader.getXmpMetadata>`
"""
def __init__(self, stream):
self.stream = stream
docRoot = parseString(self.stream.getData())
self.rdfRoot = docRoot.getElementsByTagNameNS(RDF_NAMESPACE, "RDF")[0]
self.cache = {}
def writeToStream(self, stream, encryption_key):
self.stream.writeToStream(stream, encryption_key)
def getElement(self, aboutUri, namespace, name):
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
attr = desc.getAttributeNodeNS(namespace, name)
if attr != None:
yield attr
for element in desc.getElementsByTagNameNS(namespace, name):
yield element
def getNodesInNamespace(self, aboutUri, namespace):
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
for i in range(desc.attributes.length):
attr = desc.attributes.item(i)
if attr.namespaceURI == namespace:
yield attr
for child in desc.childNodes:
if child.namespaceURI == namespace:
yield child
def _getText(self, element):
text = ""
for child in element.childNodes:
if child.nodeType == child.TEXT_NODE:
text += child.data
return text
def _converter_string(value):
return value
def _converter_date(value):
m = iso8601.match(value)
year = int(m.group("year"))
month = int(m.group("month") or "1")
day = int(m.group("day") or "1")
hour = int(m.group("hour") or "0")
minute = int(m.group("minute") or "0")
second = decimal.Decimal(m.group("second") or "0")
seconds = second.to_integral(decimal.ROUND_FLOOR)
milliseconds = (second - seconds) * 1000000
tzd = m.group("tzd") or "Z"
dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds)
if tzd != "Z":
tzd_hours, tzd_minutes = [int(x) for x in tzd.split(":")]
tzd_hours *= -1
if tzd_hours < 0:
tzd_minutes *= -1
dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes)
return dt
_test_converter_date = staticmethod(_converter_date)
def _getter_bag(namespace, name, converter):
def get(self):
cached = self.cache.get(namespace, {}).get(name)
if cached:
return cached
retval = []
for element in self.getElement("", namespace, name):
bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag")
if len(bags):
for bag in bags:
for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
value = self._getText(item)
value = converter(value)
retval.append(value)
ns_cache = self.cache.setdefault(namespace, {})
ns_cache[name] = retval
return retval
return get
def _getter_seq(namespace, name, converter):
def get(self):
cached = self.cache.get(namespace, {}).get(name)
if cached:
return cached
retval = []
for element in self.getElement("", namespace, name):
seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq")
if len(seqs):
for seq in seqs:
for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
value = self._getText(item)
value = converter(value)
retval.append(value)
else:
value = converter(self._getText(element))
retval.append(value)
ns_cache = self.cache.setdefault(namespace, {})
ns_cache[name] = retval
return retval
return get
def _getter_langalt(namespace, name, converter):
def get(self):
cached = self.cache.get(namespace, {}).get(name)
if cached:
return cached
retval = {}
for element in self.getElement("", namespace, name):
alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt")
if len(alts):
for alt in alts:
for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
value = self._getText(item)
value = converter(value)
retval[item.getAttribute("xml:lang")] = value
else:
retval["x-default"] = converter(self._getText(element))
ns_cache = self.cache.setdefault(namespace, {})
ns_cache[name] = retval
return retval
return get
def _getter_single(namespace, name, converter):
def get(self):
cached = self.cache.get(namespace, {}).get(name)
if cached:
return cached
value = None
for element in self.getElement("", namespace, name):
if element.nodeType == element.ATTRIBUTE_NODE:
value = element.nodeValue
else:
value = self._getText(element)
break
if value != None:
value = converter(value)
ns_cache = self.cache.setdefault(namespace, {})
ns_cache[name] = value
return value
return get
dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor", _converter_string))
"""
Contributors to the resource (other than the authors). An unsorted
array of names.
"""
dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage", _converter_string))
"""
Text describing the extent or scope of the resource.
"""
dc_creator = property(_getter_seq(DC_NAMESPACE, "creator", _converter_string))
"""
A sorted array of names of the authors of the resource, listed in order
of precedence.
"""
dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date))
"""
A sorted array of dates (datetime.datetime instances) of signifigance to
the resource. The dates and times are in UTC.
"""
dc_description = property(_getter_langalt(DC_NAMESPACE, "description", _converter_string))
"""
A language-keyed dictionary of textual descriptions of the content of the
resource.
"""
dc_format = property(_getter_single(DC_NAMESPACE, "format", _converter_string))
"""
The mime-type of the resource.
"""
dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier", _converter_string))
"""
Unique identifier of the resource.
"""
dc_language = property(_getter_bag(DC_NAMESPACE, "language", _converter_string))
"""
An unordered array specifying the languages used in the resource.
"""
dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher", _converter_string))
"""
An unordered array of publisher names.
"""
dc_relation = property(_getter_bag(DC_NAMESPACE, "relation", _converter_string))
"""
An unordered array of text descriptions of relationships to other
documents.
"""
dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights", _converter_string))
"""
A language-keyed dictionary of textual descriptions of the rights the
user has to this resource.
"""
dc_source = property(_getter_single(DC_NAMESPACE, "source", _converter_string))
"""
Unique identifier of the work from which this resource was derived.
"""
dc_subject = property(_getter_bag(DC_NAMESPACE, "subject", _converter_string))
"""
An unordered array of descriptive phrases or keywrods that specify the
topic of the content of the resource.
"""
dc_title = property(_getter_langalt(DC_NAMESPACE, "title", _converter_string))
"""
A language-keyed dictionary of the title of the resource.
"""
dc_type = property(_getter_bag(DC_NAMESPACE, "type", _converter_string))
"""
An unordered array of textual descriptions of the document type.
"""
pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords", _converter_string))
"""
An unformatted text string representing document keywords.
"""
pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion", _converter_string))
"""
The PDF file version, for example 1.0, 1.3.
"""
pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer", _converter_string))
"""
The name of the tool that created the PDF document.
"""
xmp_createDate = property(_getter_single(XMP_NAMESPACE, "CreateDate", _converter_date))
"""
The date and time the resource was originally created. The date and
time are returned as a UTC datetime.datetime object.
"""
xmp_modifyDate = property(_getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date))
"""
The date and time the resource was last modified. The date and time
are returned as a UTC datetime.datetime object.
"""
xmp_metadataDate = property(_getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date))
"""
The date and time that any metadata for this resource was last
changed. The date and time are returned as a UTC datetime.datetime
object.
"""
xmp_creatorTool = property(_getter_single(XMP_NAMESPACE, "CreatorTool", _converter_string))
"""
The name of the first known tool used to create the resource.
"""
xmpmm_documentId = property(_getter_single(XMPMM_NAMESPACE, "DocumentID", _converter_string))
"""
The common identifier for all versions and renditions of this resource.
"""
xmpmm_instanceId = property(_getter_single(XMPMM_NAMESPACE, "InstanceID", _converter_string))
"""
An identifier for a specific incarnation of a document, updated each
time a file is saved.
"""
def custom_properties(self):
if not hasattr(self, "_custom_properties"):
self._custom_properties = {}
for node in self.getNodesInNamespace("", PDFX_NAMESPACE):
key = node.localName
while True:
# see documentation about PDFX_NAMESPACE earlier in file
idx = key.find(u_("\u2182"))
if idx == -1:
break
key = key[:idx] + chr(int(key[idx+1:idx+5], base=16)) + key[idx+5:]
if node.nodeType == node.ATTRIBUTE_NODE:
value = node.nodeValue
else:
value = self._getText(node)
self._custom_properties[key] = value
return self._custom_properties
custom_properties = property(custom_properties)
"""
Retrieves custom metadata properties defined in the undocumented pdfx
metadata schema.
:return: a dictionary of key/value items for custom metadata properties.
:rtype: dict
"""

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from .PyPDF2 import PdfFileReader, PdfFileWriter
from .pdf import Pdf

352
PdfFileTransformer/pdf.py Normal file
View File

@@ -0,0 +1,352 @@
# -*- coding: utf-8 -*-
import logging
import re
import tempfile
from .PyPDF2 import PdfFileWriter, PdfFileReader
class Pdf:
def __init__(self, filename):
self.filename = filename
self.buffer = bytearray()
self.objects = [] # [(7,0,b"data"), (8,0,b"data2"), ..]
self.trailer = {} # {Root: (7, 0), Info: (5, 0)}
self.translation_table = {} # {(6,0):7, (5,0): 8}, ..]
self.original_xref_offset = 0
self.original_first_obj_offset = 0
self.file_offset = 0
self.clean_and_read_pdf()
self.check_pdf_header()
self.parse_xref_offset()
self.parse_xref_table()
self.parse_objects()
self.parse_trailer()
def clean_and_read_pdf(self):
f_input = open(self.filename, "rb")
pdf_header = f_input.read(8)
f_input.seek(0)
filename_output = tempfile.mktemp()
logging.info("Use " + filename_output + " for normalisation output")
f_ouput = open(filename_output, "wb")
writer = PdfFileWriter()
reader = PdfFileReader(f_input)
info = reader.getDocumentInfo()
if info.producer is not None:
writer.addMetadata({u'/Producer': info.producer})
else:
writer.addMetadata({u'/Producer': u'TruePolyglot'})
if info.creator is not None:
writer.addMetadata({u'/Creator': info.creator})
else:
writer.addMetadata({u'/Creator': u'TruePolyglot'})
writer.appendPagesFromReader(reader)
writer.setHeader(pdf_header)
writer.write(f_ouput)
f_input.close()
f_ouput.close()
f_norm = open(filename_output, "rb")
self.buffer = bytearray(f_norm.read())
self.size = len(self.buffer)
f_norm.close()
def check_pdf_header(self):
if self.buffer[0:5] == b"%PDF-":
pdf_version = self.buffer[5:8].decode("utf-8")
logging.info("PDF Header found: " + pdf_version)
else:
raise Exception("PDF Header not found")
def parse_xref_offset(self):
r = re.compile(b'startxref\n([0-9]+)')
m = r.search(self.buffer)
if m is None:
raise Exception('Unable to find xref offset')
self.original_xref_offset = int(m.group(1))
logging.info("Xref offset found at: " + hex(self.original_xref_offset))
def parse_xref_table(self):
xref_table = []
r = re.compile(b'xref\n([0-9]+) ([0-9]+)')
offset = self.original_xref_offset
s = r.search(self.buffer[offset:offset + 32])
nb_xtable_object = int(s.group(2))
logging.info("Nb objects in Xref table: " + str(nb_xtable_object))
xref_header_size = s.end()
r = re.compile(b'([0-9]+) ([0-9]+) ([f|n])')
x = 0
for i in range(nb_xtable_object):
s = r.search(
self.buffer[self.original_xref_offset + xref_header_size + x:])
if s is not None:
x = x + s.end()
xref_table.append((int(s.group(1)),
int(s.group(2)),
s.group(3)))
logging.debug("Xref table:")
for i in xref_table:
logging.debug(str(i[0]) + " " +
str(i[1]) + " " +
i[2].decode("utf-8"))
def parse_objects(self):
r_begin = re.compile(b'([0-9]+) ([0-9]+) obj\n')
r_end = re.compile(b'\nendobj\n')
offset_buffer = 0
obj = ()
while offset_buffer < self.size:
m_begin = r_begin.match(
self.buffer[offset_buffer:offset_buffer + 32])
obj_nb_index = 0
obj_nb_offset = 0
obj_offset_start = 0
obj_offset_end = 0
if m_begin is not None:
if self.original_first_obj_offset == 0:
self.original_first_obj_offset = (offset_buffer +
m_begin.start())
obj_nb_index = int(m_begin.group(1))
obj_nb_offset = int(m_begin.group(2))
obj_data_start = m_begin.end()
obj_offset_start = offset_buffer + m_begin.start()
while offset_buffer < self.size:
m_end = r_end.match(
self.buffer[offset_buffer:offset_buffer + 8])
if m_end is not None:
obj_offset_end = offset_buffer + m_end.end() - 2
break
else:
offset_buffer = offset_buffer + 1
else:
offset_buffer = offset_buffer + 1
if (obj_offset_start != 0 and
obj_offset_end != 0):
a = obj_offset_start + obj_data_start
b = obj_offset_end - 6
obj = (obj_nb_index, obj_nb_offset,
self.buffer[a:b])
logging.debug("Objects: (" + str(obj_nb_index) +
", " + str(obj_nb_offset) +
", " + hex(obj_offset_start) +
", " + hex(obj_offset_end))
self.objects.append(obj)
def parse_trailer(self):
r_begin = re.compile(b'trailer\n')
s_begin = r_begin.search(self.buffer[self.original_xref_offset:])
start = self.original_xref_offset + s_begin.start()
logging.info("Trailer found at:" + hex(start))
r_root = re.compile(b'/Root ([0-9]+) ([0-9]+) R')
s_root = r_root.search(self.buffer[self.original_xref_offset:])
if s_root is None:
raise Exception('Root not found')
else:
self.trailer["Root"] = (int(s_root.group(1)), int(s_root.group(2)))
r_info = re.compile(b'/Info ([0-9]+) ([0-9]+) R')
s_info = r_info.search(self.buffer[self.original_xref_offset:])
if s_info is not None:
self.trailer["Info"] = (int(s_info.group(1)), int(s_info.group(2)))
def get_file_header(self):
return self.buffer[:self.original_first_obj_offset]
def get_xref_table(self):
offset_xref = 0
buf = (b'xref\n' +
str(offset_xref).encode('utf-8') + b' ' +
str(len(self.objects) + 1).encode('utf-8') + b'\n' +
str(0).zfill(10).encode('utf-8') + b' ' +
str(65535).zfill(5).encode('utf-8') + b' f \n')
for i in range(len(self.objects)):
obj_start = self.get_object_offset(i)
logging.info("Obj %d at %d" % (self.objects[i][0], obj_start))
buf = (buf +
(str(obj_start).zfill(10)).encode('utf-8') + b' ' +
str(0).zfill(5).encode('utf-8') + b' ' +
b'n' + b' \n')
return buf
def get_trailer(self):
trailer_data = (b"trailer\n<<\n/Size " +
str(len(self.objects) + 1).encode("utf-8") +
b"\n/Root " +
str(self.trailer["Root"][0]).encode("utf-8") +
b" " +
str(self.trailer["Root"][1]).encode("utf-8") +
b" R\n")
if "Info" in self.trailer:
trailer_data = (trailer_data +
b"/Info " +
str(self.trailer["Info"][0]).encode("utf-8") +
b" " +
str(self.trailer["Info"][1]).encode("utf-8") +
b" R\n")
trailer_data = trailer_data + b">>"
return trailer_data
def get_xref_offset(self):
return self.get_end_of_last_object() + 1
def get_eof(self):
s = (b'startxref\n' +
str(self.get_xref_offset()).encode("utf-8") +
b'\n%%EOF\n')
return s
def build_object(self, obj):
buf = (str(obj[0]).encode("utf-8") +
b' ' +
str(obj[1]).encode("utf-8") +
b' obj\n' +
obj[2] +
b'\nendobj')
return buf
def get_build_buffer(self):
b_buffer = bytearray()
b_buffer = b_buffer + self.get_file_header()
for obj in self.objects:
b_buffer = b_buffer + self.build_object(obj) + b'\n'
b_buffer = b_buffer + self.get_xref_table()
b_buffer = b_buffer + self.get_trailer() + b'\n'
b_buffer = b_buffer + self.get_eof()
return b_buffer
def get_obj(self, nb):
for obj in self.objects:
if obj[0] == nb:
return obj
def get_end_of_last_object(self):
offset = self.get_last_object_offset()
offset = offset + len(self.build_object(self.objects[-1]))
return offset
def generate_stream_obj_data(self, data):
buf = (b'<<\n/Filter /FlateDecode\n/Length ' +
str(len(data)).encode("utf-8") +
b'\n>>\nstream\n' +
data +
b'\nendstream')
return buf
def insert_new_obj_stream_at(self, position, stream_data):
'''
Return offset of stream data
'''
logging.info("Insert obj at %d" % position)
obj_nb = position
obj_off = 0
data = self.generate_stream_obj_data(stream_data)
obj = (obj_nb, obj_off, data)
obj_data = self.build_object(obj)
full_obj_size = len(obj_data)
logging.info("New object full size is: " + str(full_obj_size))
obj = (obj_nb, obj_off, data)
self.objects.insert(position, obj)
self.reorder_objects()
self.fix_trailer_ref()
def get_first_stream_offset(self):
offset = self.file_offset + len(self.get_file_header())
r = re.compile(b'stream\n')
m = r.search(self.objects[0][2])
offset = offset + len(b"1 0 obj\n") + m.end()
return offset
def get_last_stream_offset(self):
offset = self.file_offset + self.get_last_object_offset()
r = re.compile(b'stream\n')
m = r.search(self.build_object(self.objects[-1]))
return offset + m.end()
def get_object_offset(self, index):
offset = self.file_offset + len(self.get_file_header())
for obj in self.objects[:index]:
offset = offset + len(self.build_object(obj)) + 1
return offset
def get_last_object_offset(self):
offset = self.get_object_offset(len(self.objects) - 1)
return offset
def insert_new_obj_stream_at_start(self, data):
return self.insert_new_obj_stream_at(0, data)
def insert_new_obj_stream_at_end(self, data):
return self.insert_new_obj_stream_at(len(self.objects) + 1,
data)
def generate_translation_table(self):
for i in range(len(self.objects)):
self.translation_table[(self.objects[i][0],
self.objects[i][1])] = i + 1
logging.info(self.translation_table)
def replace_ref(self, ibuffer):
'''
Exemple:
in: AZERTY 6 0 R -- BGT 88 0 R HYT
out: AZERTY 77 0 R -- BGT 9 0 R HYT
'''
index = 0
obuffer = bytearray()
while True:
r = re.compile(b'([0-9]+) ([0-9]+) R')
s = r.search(ibuffer[index:])
if s is None:
obuffer = obuffer + ibuffer[index:]
break
o_old = int(s.group(1))
p_old = int(s.group(2))
o_new = self.translation_table[(o_old, p_old)]
p_new = p_old
newref = (str(o_new).encode("utf-8") +
b" " +
str(p_new).encode("utf-8") +
b" R")
nbuffer = ibuffer[index:index + s.start()] + newref
obuffer = obuffer + nbuffer
index = index + s.end()
return obuffer
def reorder_objects(self):
self.generate_translation_table()
offset_obj = len(self.get_file_header())
for i in range(len(self.objects)):
buf = self.objects[i][2]
new_buf = self.replace_ref(buf)
obj_nb = self.objects[i][0]
new_obj_nb = self.translation_table[(obj_nb, 0)]
new_obj_start = offset_obj
size_obj = len(self.build_object((new_obj_nb,
0,
new_buf)))
new_obj_end = new_obj_start + size_obj
offset_obj = new_obj_end + 1
obj = (new_obj_nb,
0,
new_buf)
self.objects[i] = obj
def fix_trailer_ref(self):
new_obj_nb = self.translation_table[self.trailer["Root"]]
self.trailer["Root"] = (new_obj_nb, 0)
if "Info" in self.trailer:
new_obj_nb = self.translation_table[self.trailer["Info"]]
self.trailer["Info"] = (new_obj_nb, 0)

7
PolyglotFile/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from .polyglotpdfzip import PolyglotPdfZip
from .polyglotzippdf import PolyglotZipPdf
from .polyglotszippdf import PolyglotSZipPdf

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
import logging
'''
|-------------------------------| -
|--------- PDF Header ----------K1 | J1
|-------------------------------| -
|----- PDF OBJ 1 = ZIP Data ----K2 |
|-------------------------------| -
|---- Original PDF Ojbects -----K3 | J2
|-------------------------------| -
|--- Last OBJ = End Zip Data ---K4 |
|-------------------------------| |
|---------- Xref Table ---------| |
|-------------------------------K5 |
|----------- Trailer -----------| |
|-------------------------------| |
'''
class PolyglotPdfZip():
from PdfFileTransformer import Pdf
from ZipFileTransformer import Zip
def __init__(self, Pdf, Zip):
self.buffer = bytearray()
self.pdf = Pdf
self.zip = Zip
self.buffer = bytearray()
def generate(self):
k2_stream = self.zip.buffer[:self.zip.end_of_data]
size_k2_stream = len(k2_stream)
self.pdf.insert_new_obj_stream_at_start(k2_stream)
offset_k2_stream = self.pdf.get_first_stream_offset()
k4_stream = self.zip.buffer[self.zip.central_dir_file_header:]
size_k4_stream = len(k4_stream)
self.pdf.insert_new_obj_stream_at_end(k4_stream)
offset_k4_stream = self.pdf.get_last_stream_offset()
pdf_buffer = self.pdf.get_build_buffer()
j1 = pdf_buffer[0:offset_k2_stream]
j2 = pdf_buffer[offset_k2_stream + size_k2_stream:offset_k4_stream]
self.zip.add_data_to_file(j1, j2, True)
k5 = pdf_buffer[offset_k4_stream + size_k4_stream:]
self.buffer = self.zip.buffer + k5
def write(self, filename):
fd = open(filename, "wb")
fd.write(self.buffer)
fd.close()

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
from .polyglotpdfzip import PolyglotPdfZip
import logging
import tempfile
from ZipFileTransformer import ZipFile
from ZipFileTransformer import Zip
from PdfFileTransformer import Pdf
'''
|-----------------------------------| -
|--------- ZIP Data[0] = -----------| |
|- PDF Header + PDF Obj[0] Header --| |
|-----------------------------------| | K2
|------- PDF Obj[0] stream = ------| |
|--------- ZIP Data LF [1:] --------| |
|-----------------------------------| -
|------ Original PDF Ojbects -------| |
|-----------------------------------| |
|------------ Xref Table -----------| |
|-----------------------------------| | J2
|------------- Trailer -------------| |
|-----------------------------------| -
|---------- End Zip Data -----------|
|-----------------------------------|
'''
class PolyglotSZipPdf(PolyglotPdfZip):
def __init__(self, Pdf, Zip):
super().__init__(Pdf, Zip)
def get_rebuild_zip_first_part_size(self):
zo_path = tempfile.mkstemp()[1]
logging.info("use tmp file zip: " + zo_path)
zo = ZipFile(zo_path, 'a')
zi = ZipFile(self.zip.filename, 'r')
for zipinfo in zi.infolist():
zo.writestr(zipinfo, zi.read(zipinfo))
zi.close()
zo.close()
rebuild_zip = Zip(zo_path)
p = rebuild_zip.end_of_data
k2_stream = rebuild_zip.buffer[:p]
size_k2_stream = len(k2_stream)
return size_k2_stream
def get_pdf_header(self):
return self.pdf.get_file_header()
def generate_zip_with_pdf_part(self, filename, pdf_data):
zo = ZipFile(filename, 'a')
zi = ZipFile(self.zip.filename, 'r')
zo.writestr(' ', pdf_data, 0)
for zipinfo in zi.infolist():
zo.writestr(zipinfo, zi.read(zipinfo))
zi.close()
zo.close()
def get_rebuild_pdf(self, zo_path, offset):
'''
Generate polyglot with final zip.
'''
new_zip = Zip(zo_path)
new_pdf = Pdf(self.pdf.filename)
p1 = new_zip.end_of_first_local_file_header
p2 = new_zip.end_of_data
k2_stream = new_zip.buffer[p1:p2]
size_k2_stream = len(k2_stream)
new_pdf.insert_new_obj_stream_at_start(k2_stream)
k2_stream_offset = new_pdf.get_first_stream_offset()
new_pdf.file_offset = offset
pdf_buffer = new_pdf.get_build_buffer()
j2 = pdf_buffer[k2_stream_offset + size_k2_stream:]
new_zip.add_data_to_file(b'', j2, True)
return new_zip.buffer
def get_pdf_offset(self, zipfile):
f = open(zipfile, "rb")
data = f.read()
return data.find(b"%PDF")
def generate(self):
zip_stream_size = self.get_rebuild_zip_first_part_size()
pdf_header = self.get_pdf_header()
pdf_header = (pdf_header +
b'1 0 obj\n<<\n/Filter /FlateDecode\n/Length ' +
str(zip_stream_size).encode("utf-8") +
b'\n>>\nstream\n')
filename = tempfile.mkstemp()[1]
logging.info("use tmp file for new zip: " + filename)
self.generate_zip_with_pdf_part(filename, pdf_header)
pdf_offset = self.get_pdf_offset(filename)
self.buffer = self.get_rebuild_pdf(filename, pdf_offset)

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from .polyglotpdfzip import PolyglotPdfZip
'''
|-------------------------------| -
|--------- PDF Header ----------K1 | J1
|-------------------------------| -
|----- PDF OBJ 1 = ZIP Data ----K2 |
|-------------------------------| -
|---- Original PDF Ojbects -----K3 |
|-------------------------------| |
|---------- Xref Table ---------| |
|-------------------------------K4 | J2
|----------- Trailer -----------| |
|-------------------------------| -
|-------- End Zip Data ---------| |
|-------------------------------| |
'''
class PolyglotZipPdf(PolyglotPdfZip):
def generate(self):
k2_stream = self.zip.buffer[:self.zip.end_of_data]
size_k2_stream = len(k2_stream)
self.pdf.insert_new_obj_stream_at_start(k2_stream)
offset_k2_stream = self.pdf.get_first_stream_offset()
pdf_buffer = self.pdf.get_build_buffer()
j1 = pdf_buffer[0:offset_k2_stream]
j2 = pdf_buffer[offset_k2_stream + size_k2_stream:]
self.zip.add_data_to_file(j1, j2, True)
self.buffer = self.zip.buffer

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# TruePolyglot
See webiste at https://truepolyglot.hackade.org

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from .zip import Zip
from .zipfile import *

227
ZipFileTransformer/zip.py Normal file
View File

@@ -0,0 +1,227 @@
# -*- coding: utf-8 -*-
import logging
import re
class Zip:
def __init__(self, filename):
self.filename = filename
self.buffer = bytearray()
self.size = 0
self.end_central_dir = 0
self.first_local_file_header = 0
self.offset_local_file = []
self.offset_central_directory = []
self.end_of_data = 0
self.end_of_first_local_file_header = 0
self.read()
self.check_header()
self.call_all_parsers()
self.check_central_directory()
self.parse_central_directories()
self.parse_local_file_headers()
def call_all_parsers(self):
self.parse_offset_end_central_dir()
self.parse_nb_of_disk()
self.parse_start_disk()
self.parse_nb_of_central_dir()
self.parse_nb_total_of_central_dir()
self.parse_size_central_dir()
self.parse_central_dir_file_header()
self.parse_comment_length()
def read(self):
with open(self.filename, 'rb') as fd:
self.buffer = bytearray(fd.read())
self.size = len(self.buffer)
logging.info("read " + str(self.size) + " bytes from Zip file")
def check_header(self):
if self.buffer[0:4] != b"PK\x03\x04":
raise Exception("Zip header not found")
def parse_offset_end_central_dir(self):
r = re.compile(b'\x06\x05KP')
s = r.search(self.buffer[::-1])
if s is None:
raise Exception("Unable to find end of central directory")
self.end_central_dir = self.size - s.end()
logging.info("Offset end of central directory: " +
hex(self.end_central_dir))
def parse_nb_of_disk(self):
self.nb_of_disk = int.from_bytes(
self.buffer[self.end_central_dir + 4:self.end_central_dir + 6],
"little")
logging.debug("Nb of disk: " + str(self.nb_of_disk))
def parse_start_disk(self):
self.start_disk = int.from_bytes(
self.buffer[self.end_central_dir + 6:self.end_central_dir + 8],
"little")
logging.debug("Start disk: " + str(self.start_disk))
def parse_nb_of_central_dir(self):
self.nb_of_central_dir = int.from_bytes(
self.buffer[self.end_central_dir + 8:self.end_central_dir + 10],
"little")
logging.info("Nb of central directory record: " +
str(self.nb_of_central_dir))
def parse_nb_total_of_central_dir(self):
self.nb_total_of_central_dir = int.from_bytes(
self.buffer[self.end_central_dir + 10:self.end_central_dir + 12],
"little")
logging.info("Nb of total central directory record: " +
str(self.nb_total_of_central_dir))
def parse_size_central_dir(self):
self.size_central_dir = int.from_bytes(
self.buffer[self.end_central_dir + 12:self.end_central_dir + 14],
"little")
logging.info("Size of central directory: " +
str(self.size_central_dir))
def parse_central_dir_file_header(self):
self.central_dir_file_header = int.from_bytes(
self.buffer[self.end_central_dir + 16:self.end_central_dir + 20],
"little")
logging.info("Central directory file header: " +
hex(self.central_dir_file_header))
def parse_comment_length(self):
self.comment_length = int.from_bytes(
self.buffer[self.end_central_dir + 20:self.end_central_dir + 22],
"little")
logging.info("Comment length: " +
str(self.comment_length))
def check_central_directory(self):
offset = self.central_dir_file_header
if (self.buffer[offset:offset + 4] !=
b'PK\x01\x02'):
raise Exception("Unable to find central directory")
logging.info("Found central directory")
def parse_central_directories(self):
if (self.buffer[self.central_dir_file_header:
self.central_dir_file_header + 4] !=
b'PK\x01\x02'):
raise Exception("Unable to find first central directory")
logging.info("Found first central directory")
i = 0
size = 0
offset = self.central_dir_file_header
while (self.buffer[size + offset:
size + offset + 4] ==
b'PK\x01\x02'):
logging.info("Parse central directory n°" + str(i))
logging.info("Offset: " + hex(offset + size))
self.offset_central_directory.append(offset + size)
filename_length = int.from_bytes(
self.buffer[size + offset + 28:size + offset + 30],
"little")
logging.info("filename length:" + str(filename_length))
extra_field_length = int.from_bytes(
self.buffer[size + offset + 30:size + offset + 32],
"little")
logging.info("extra field length:" + str(extra_field_length))
comment_length = int.from_bytes(
self.buffer[size + offset + 32:size + offset + 34],
"little")
logging.info("comment length:" + str(comment_length))
local_file_header = int.from_bytes(
self.buffer[size + offset + 42:size + offset + 46],
"little")
if i == 0:
self.first_local_file_header = local_file_header
logging.info("local file header:" + hex(local_file_header))
i = i + 1
size = (size + filename_length +
extra_field_length + comment_length + 46)
logging.debug("parse header at:" + hex(offset + size))
def parse_local_file_headers(self):
size = 0
offset = self.first_local_file_header
for i in range(self.nb_of_central_dir):
logging.info("Parse local file n°" + str(i))
compressed_data_lenght = int.from_bytes(
self.buffer[size + offset + 18:size + offset + 22],
"little")
logging.info("compressed data length:" +
str(compressed_data_lenght))
filename_length = int.from_bytes(
self.buffer[size + offset + 26:size + offset + 28],
"little")
logging.info("filename length:" + str(filename_length))
extra_field_length = int.from_bytes(
self.buffer[size + offset + 28:size + offset + 30],
"little")
logging.info("extra field length:" + str(extra_field_length))
local_file_size = (compressed_data_lenght +
filename_length + extra_field_length + 30)
logging.info("local file length:" + hex(local_file_size))
size = size + local_file_size
logging.debug("parse header at:" + hex(offset + size))
self.offset_local_file.append(offset + size)
self.end_of_data = offset + size
if i == 0:
self.end_of_first_local_file_header = self.end_of_data
def add_data_to_file(self, data_before_local, data_after_local,
write_buffer=False):
logging.info("Add data before local lenght:" +
str(len(data_before_local)))
new_buffer = self.buffer
for i in self.offset_central_directory:
logging.info("parse central directory at: " + hex(i))
local_file_header = int.from_bytes(
self.buffer[i + 42:i + 46],
"little")
logging.info("old local file header: " + hex(local_file_header))
local_file_header = local_file_header + len(data_before_local)
logging.info("new local file header: " + hex(local_file_header))
bytes_local_file_header = local_file_header.to_bytes(4, "little")
logging.info("change value at:" + hex(i + 42))
new_buffer[i + 42:i + 46] = bytes_local_file_header
logging.info("old central directory header: " +
hex(self.central_dir_file_header))
new_central_dir_file_header = (self.central_dir_file_header +
len(data_after_local) +
len(data_before_local))
logging.info("new central directory header: " +
hex(new_central_dir_file_header))
bytes_offset = new_central_dir_file_header.to_bytes(4, "little")
new_buffer[self.end_central_dir + 16:
self.end_central_dir + 20] = bytes_offset
self.buffer = new_buffer
if write_buffer:
new_buffer = (data_before_local +
new_buffer[:self.end_of_data] +
data_after_local +
new_buffer[self.central_dir_file_header:])
self.buffer = new_buffer
def get_local_file_data(self):
return self.buffer[:self.end_of_data]
def get_data_after_central_directory(self):
return self.buffer[self.central_dir_file_header:]
def get_first_part_length(self):
return len(self.get_local_file_data())
def get_second_part_length(self):
return len(self.get_data_after_central_directory())

File diff suppressed because it is too large Load Diff

BIN
caradoc Executable file

Binary file not shown.

80
pdfcat Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python
"""
Concatenate pages from pdf files into a single pdf file.
Page ranges refer to the previously-named file.
A file not followed by a page range means all the pages of the file.
PAGE RANGES are like Python slices.
{page_range_help}
EXAMPLES
pdfcat -o output.pdf head.pdf content.pdf :6 7: tail.pdf -1
Concatenate all of head.pdf, all but page seven of content.pdf,
and the last page of tail.pdf, producing output.pdf.
pdfcat chapter*.pdf >book.pdf
You can specify the output file by redirection.
pdfcat chapter?.pdf chapter10.pdf >book.pdf
In case you don't want chapter 10 before chapter 2.
"""
# Copyright (c) 2014, Steve Witham <switham_github@mac-guyver.com>.
# All rights reserved. This software is available under a BSD license;
# see https://github.com/mstamy2/PyPDF2/LICENSE
from __future__ import print_function
import argparse
from PdfFileTransformer.PyPDF2.pagerange import PAGE_RANGE_HELP
def parse_args():
parser = argparse.ArgumentParser(
description=__doc__.format(page_range_help=PAGE_RANGE_HELP),
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-o", "--output",
metavar="output_file")
parser.add_argument("-v", "--verbose", action="store_true",
help="show page ranges as they are being read")
parser.add_argument("first_filename", nargs=1,
metavar="filename [page range...]")
# argparse chokes on page ranges like "-2:" unless caught like this:
parser.add_argument("fn_pgrgs", nargs=argparse.REMAINDER,
metavar="filenames and/or page ranges")
args = parser.parse_args()
args.fn_pgrgs.insert(0, args.first_filename[0])
return args
from sys import stderr, stdout, exit
import os
import traceback
from collections import defaultdict
from PdfFileTransformer.PyPDF2 import PdfFileMerger, parse_filename_page_ranges
if __name__ == "__main__":
args = parse_args()
filename_page_ranges = parse_filename_page_ranges(args.fn_pgrgs)
if args.output:
output = open(args.output, "wb")
else:
stdout.flush()
output = os.fdopen(stdout.fileno(), "wb")
merger = PdfFileMerger()
in_fs = dict()
try:
for (filename, page_range) in filename_page_ranges:
if args.verbose:
print(filename, page_range, file=stderr)
if filename not in in_fs:
in_fs[filename] = open(filename, "rb")
merger.append(in_fs[filename], pages=page_range)
except:
print(traceback.format_exc(), file=stderr)
print("Error while reading " + filename, file=stderr)
exit(1)
merger.write(output)
# In 3.0, input files must stay open until output is written.
# Not closing the in_fs because this script exits now.

View File

@@ -0,0 +1,7 @@
== Zip files ==
test1.zip: deux fichiers et commentaire global.
== Pdf files ==
test1.pdf: fichier des impots.

BIN
tests/samples/test1.pdf Normal file

Binary file not shown.

BIN
tests/samples/test1.zip Normal file

Binary file not shown.

Binary file not shown.

21
tests/test_pdf_add_data.py Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.path.append("../")
import logging
from PdfFileTransformer import Pdf
input_file = "./samples/test1.pdf"
output_file = "./samples/test1_out.pdf"
logging.basicConfig(level=logging.DEBUG)
p = Pdf(input_file)
p.insert_new_obj_stream_at_start(b'A' * 140)
p.insert_new_obj_stream_at_end(b'B' * 120)
f = open(output_file, 'wb')
f.write(p.get_build_buffer())
f.close()

26
tests/test_pdf_normalisation.py Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.path.append("../")
import logging
from PdfFileTransformer.PyPDF2 import PdfFileReader, PdfFileWriter
input_file = "./samples/test1.pdf"
output_file = "./samples/test1_out.pdf"
logging.basicConfig(level=logging.DEBUG)
f_input = open(input_file, "rb")
reader = PdfFileReader(f_input)
f_output = open(output_file, "wb")
writer = PdfFileWriter()
writer.appendPagesFromReader(reader)
writer.setHeader(b"%PDF-1.5")
writer.write(f_output)
f_input.close()
f_output.close()

19
tests/test_pdf_rebuild.py Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.path.append("../")
from PdfFileTransformer import Pdf
import logging
input_file = "./samples/test1.pdf"
output_file = "./samples/test1_out.pdf"
logging.basicConfig(level=logging.DEBUG)
p = Pdf(input_file)
f = open(output_file, 'wb')
f.write(p.get_build_buffer())
f.close()

23
tests/test_polyglot_pdfzip.py Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.path.append("../")
from PdfFileTransformer import Pdf
from ZipFileTransformer import Zip
from PolyglotFile import PolyglotPdfZip
import logging
input_file_pdf = "./samples/test1.pdf"
input_file_zip = "./samples/test1.zip"
output_file = "./samples/test1_out.pdf"
logging.basicConfig(level=logging.DEBUG)
p = Pdf(input_file_pdf)
z = Zip(input_file_zip)
a = PolyglotPdfZip(p, z)
a.generate()
a.write(output_file)

20
tests/test_rebuild_zip.py Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.path.append("../")
import tempfile
from ZipFileTransformer import Zip, ZipFile
input_file = "./samples/test1.zip"
output_file = "./samples/test1_out.zip"
zi = ZipFile(input_file,"r")
zo = ZipFile(output_file,"w")
zo.writestr(' ',b'AAAAAAAAAAAAAAAAAAAAAA',0)
for zipinfo in zi.infolist():
zo.writestr(zipinfo, zi.read(zipinfo))
zi.close()
zo.close()

21
tests/test_zip.py Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.path.append("../")
import tempfile
from ZipFileTransformer import Zip
input_file = "./samples/test1.zip"
output_file = tempfile.mktemp()
print("Output: " + output_file)
z = Zip(input_file)
a = bytearray(b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
b = bytearray(b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')
z.add_data_to_file(a, b, False)
g = open(output_file, "wb")
g.write(a + z.get_local_file_data() + b + z.get_data_after_central_directory())
g.close()

74
truepolyglot Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import logging
from PdfFileTransformer import Pdf
from ZipFileTransformer import Zip
from PolyglotFile import PolyglotZipPdf, PolyglotPdfZip, PolyglotSZipPdf
def main():
description_str = ('Generate a polyglot file.\n\nFormats availables:\n' +
'* pdfzip: Generate a file valid as PDF and ZIP.' +
' The format is closest to PDF.\n' +
'* zippdf: Generate a file valid as ZIP and PDF.' +
' The format is closest to ZIP.\n' +
'* szippdf: Generate a file valid as ZIP and PDF.' +
' The format is strictly a ZIP.' +
' Archive is modified.')
usage_str = '%(prog)s format [options] output-file'
epilog_str = 'TruePolyglot v1.3'
frm = argparse.RawTextHelpFormatter
parser = argparse.ArgumentParser(description=description_str,
epilog=epilog_str,
usage=usage_str,
formatter_class=frm)
parser.add_argument('format', nargs='+', choices=["pdfzip",
"zippdf",
"szippdf"],
help='Output polyglot format')
parser.add_argument('--pdffile', dest='pdffile',
help='PDF input file')
parser.add_argument('--zipfile', dest='zipfile',
help='ZIP input file')
parser.add_argument('--verbose', dest='verbose',
help='Verbosity level (default: debug)',
default="info",
choices=["none", "error", "info", "debug"])
parser.add_argument('output_file', nargs='+',
help='Output polyglot file path')
args = parser.parse_args()
formats = ["pdfzip", "zippdf", "szippdf"]
if args.format[0] in formats:
if args.pdffile is None:
parser.error('pdffile is required')
if args.zipfile is None:
parser.error('zipfile is required')
if args.verbose == "none":
logging.basicConfig(level=logging.CRITICAL)
if args.verbose == "error":
logging.basicConfig(level=logging.ERROR)
if args.verbose == "info":
logging.basicConfig(level=logging.INFO)
if args.verbose == "debug":
logging.basicConfig(level=logging.DEBUG)
p = Pdf(args.pdffile)
z = Zip(args.zipfile)
if args.format[0] == "pdfzip":
a = PolyglotPdfZip(p, z)
if args.format[0] == "zippdf":
a = PolyglotZipPdf(p, z)
if args.format[0] == "szippdf":
a = PolyglotSZipPdf(p, z)
a.generate()
a.write(args.output_file[0])
if __name__ == "__main__":
main()

63
website/css/styles.css Normal file
View File

@@ -0,0 +1,63 @@
html {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
body {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
td {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
th {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
h1 {
color: white;
}
a:link {
color: #47B8C7;
}
a:visited {
color: #47B8C7;
}
a:active {
color: #47B8C7;
}
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid white;
}
th {
background-color: #92D050;
color: black;
}
th {
padding-left: 0.5em;
padding-right: 0.5em;
padding-top: 0.5em;
padding-bottom: 0.5em;
}
td {
padding-left: 0.5em;
padding-right: 0.5em;
padding-bottom: 0.5em;
padding-top: 0.5em;
text-align: left;
}
.font_reduce {
font-size: 75%;
}
.warning {
color: #ffb833;
}

61
website/css/styles2.css Normal file
View File

@@ -0,0 +1,61 @@
html {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
body {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
td {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
th {
background-color: black;
font-family: Consolas,monaco,monospace;
color: #92D050;
}
a:link {
color: #47B8C7;
}
a:visited {
color: #47B8C7;
}
a:active {
color: #47B8C7;
}
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid white;
}
th {
background-color: #92D050;
color: black;
}
th {
padding-left: 0.5em;
padding-right: 0.5em;
padding-top: 0.5em;
padding-bottom: 0.5em;
}
td {
padding-left: 0.5em;
padding-right: 0.5em;
padding-bottom: 0.5em;
padding-top: 0.5em;
text-align: left;
}
th a:link {
color: black;
}
th a:visited {
color: black;
}
th a:active {
color: black;
}

BIN
website/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

21
website/gen_pocs.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
find -type f -name 'polyglot.pdf' -delete
mkdir -p ./samples/pdfzip/poc1/
../truepolyglot pdfzip --pdffile ./samples/pdfzip/poc1/doc.pdf --zipfile ./samples/pdfzip/poc1/archive.zip ./samples/pdfzip/poc1/polyglot.pdf
mkdir -p ./samples/pdfzip/poc2/
../truepolyglot pdfzip --pdffile ./samples/pdfzip/poc2/orwell_1984.pdf --zipfile ./samples/pdfzip/poc2/file-FILE5_32.zip ./samples/pdfzip/poc2/polyglot.pdf
mkdir -p ./samples/pdfzip/poc3/
../truepolyglot pdfzip --pdffile ./samples/pdfzip/poc3/x86asm.pdf --zipfile ./samples/pdfzip/poc3/fasmw17304.zip ./samples/pdfzip/poc3/polyglot.pdf
mkdir -p ./samples/zippdf/poc4/
../truepolyglot zippdf --pdffile ./samples/zippdf/poc4/doc.pdf --zipfile ./samples/zippdf/poc4/archive.zip ./samples/zippdf/poc4/polyglot.pdf
mkdir -p ./samples/szippdf/poc5/
../truepolyglot szippdf --pdffile ./samples/szippdf/poc5/electronics.pdf --zipfile ./samples/szippdf/poc5/hello_world.jar ./samples/szippdf/poc5/polyglot.pdf
mkdir -p ./samples/pdfzip/poc6/
../truepolyglot pdfzip --pdffile ./samples/pdfzip/poc6/hexinator.pdf --zipfile ./samples/pdfzip/poc6/eicar.zip ./samples/pdfzip/poc6/polyglot.pdf

249
website/index.html Normal file
View File

@@ -0,0 +1,249 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>TruePolyglot</title>
<meta name="description" content="TruePolyglot project website">
<meta name="author" content="hackade">
<link rel="stylesheet" href="css/styles.css">
<link rel="shortcut icon" href="/favicon.ico">
</head>
<body>
<h1>TruePolyglot</h1>
Truepolyglot is polyglot file generator project.
This means that the generated file is composed of several file formats. The same file can be opened as a ZIP file and as a PDF file for example.
The idea of this project comes from work of <a href="https://github.com/corkami">Ange Albertini</a>, <a href="https://www.alchemistowl.org/pocorgtfo/pocorgtfo07.pdf">International Journal of Proof-of-Concept or Get The Fuck Out</a> and <a href="https://www.troopers.de/wp-content/uploads/2011/04/TR11_Wolf_OMG_PDF.pdf">Julia Wolf</a> that explain how we can build a polyglot file.<br>
Polyglot file can be fastidious to build, even more if you want to respect correctly file format. That's why I decided to build a tool to generate them.<br>
My main motivation was the technical challenge.
<br>
<h2>Features and changelog</h2>
<div class="font_reduce">
<table>
<tr>
<th>Description</th>
<th>Version</th>
</tr>
<tr>
<td>Build a polyglot file valid as PDF and ZIP format and that can be opened with 7Zip and Windows Explorer</td>
<td>POC</td>
</tr>
<tr>
<td>Add a stream object in PDF part</td>
<td>POC</td>
</tr>
<tr>
<td>Polyglot file checked without warning with <a href="https://poppler.freedesktop.org/">pdftocairo</a></td>
<td> &gt;= 1.0</td>
</tr>
<tr>
<td>Polyglot file checked without warning with <a href="https://github.com/ANSSI-FR/caradoc">caradoc</a></td>
<td> &gt;= 1.0</td>
</tr>
<tr>
<td>Rebuild PDF Xref Table</td>
<td>&gt;= 1.0</td>
</tr>
<tr>
<td>Stream object with correct length header value</td>
<td>&gt;= 1.0</td>
</tr>
<tr>
<td>Format "zippdf", file without offset after Zip data</td>
<td>&gt;= 1.1</td>
</tr>
<tr>
<td>Polyglot file keep original PDF version</td>
<td>&gt;= 1.1.1</td>
</tr>
<tr>
<td>Add "szippdf" format without offset before and after Zip data</td>
<td>&gt;= 1.2</td>
</tr>
<tr>
<td>Fix /Length stream object value and PDF offset for szippdf format</td>
<td>&gt;= 1.2.1</td>
</tr>
<tr>
<td>PDF object numbers reorder after insertion</td>
<td>&gt;= 1.3</td>
</tr>
</table>
</div>
<h2>Polyglot file compatibility</h2>
<div class="font_reduce">
<table>
<tr>
<th>Software</th>
<th>Formats</th>
<th>status</th>
</tr>
<tr>
<td>Acrobat Reader</td>
<td>pdfzip, zippdf</td>
<td>OK</td>
</tr>
<tr>
<td>Acrobat Reader</td>
<td>szippdf</td>
<td><span class="warning">KO</span></td>
</tr>
<tr>
<td>Sumatra PDF</td>
<td>pdfzip, zippdf, szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>Edge</td>
<td>pdfzip, zippdf, szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>Firefox</td>
<td>pdfzip, zippdf, szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>7zip</td>
<td>pdfzip, zippdf</td>
<td><span class="warning">OK with warning</span></td>
</tr>
<tr>
<td>7zip</td>
<td>szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>Explorer Windows</td>
<td>pdfzip, zippdf, szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>Info-ZIP (unzip)</td>
<td>pdfzip, zippdf, szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>Evince</td>
<td>pdfzip, zippdf, szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>pdftocairo -pdf</td>
<td>pdfzip, zippdf, szippdf</td>
<td>OK</td>
</tr>
<tr>
<td>caradoc stats</td>
<td>pdfzip</td>
<td>OK</td>
</tr>
<tr>
<td>java</td>
<td>szippdf</td>
<td>OK</td>
</tr>
</table>
</div>
<h2>Examples</h2>
<ul>
<li><a href="/samples/">Polyglot files repository</a></li>
</ul>
<div class="font_reduce">
<table>
<tr>
<th>PDF input file</th>
<th>Zip input file</th>
<th>Format</th>
<th>Polyglot</th>
<th>Comment</th>
</tr>
<tr>
<td><a href="/samples/pdfzip/poc1/doc.pdf">doc.pdf</a></td>
<td><a href="/samples/pdfzip/poc1/archive.zip">archive.zip</a></td>
<td>pdfzip</td>
<td><a href="/samples/pdfzip/poc1/polyglot.pdf">polyglot.pdf</a></td>
<td>PDF/ZIP polyglot - 122 Ko</td>
</tr>
<tr>
<td><a href="/samples/pdfzip/poc2/orwell_1984.pdf">orwell_1984.pdf</a></td>
<td><a href="/samples/pdfzip/poc2/file-FILE5_32.zip">file-FILE5_32.zip</a></td>
<td>pdfzip</td>
<td><a href="/samples/pdfzip/poc2/polyglot.pdf">polyglot.pdf</a></td>
<td>PDF/ZIP polyglot - 1.3 Mo</td>
</tr>
<tr>
<td><a href="/samples/pdfzip/poc3/x86asm.pdf">x86asm.pdf</a></td>
<td><a href="/samples/pdfzip/poc3/fasmw17304.zip">fasmw17304.zip</a></td>
<td>pdfzip</td>
<td><a href="/samples/pdfzip/poc3/polyglot.pdf">polyglot.pdf</a></td>
<td>PDF/ZIP polyglot - 1.8 Mo</td>
</tr>
<tr>
<td><a href="/samples/zippdf/poc4/doc.pdf">doc.pdf</a></td>
<td><a href="/samples/zippdf/poc4/archive.zip">archive.zip</a></td>
<td>zippdf</td>
<td><a href="/samples/zippdf/poc4/polyglot.pdf">polyglot.pdf</a></td>
<td>PDF/ZIP polyglot - 112 Ko</td>
</tr>
<tr>
<td><a href="/samples/szippdf/poc5/electronics.pdf">electronics.pdf</a></td>
<td><a href="/samples/szippdf/poc5/hello_world.jar">hello_world.jar</a></td>
<td>szippdf</td>
<td><a href="/samples/szippdf/poc5/polyglot.pdf">polyglot.pdf</a></td>
<td>PDF/JAR polyglot - 778 Ko</td>
</tr>
<tr>
<td><a href="/samples/pdfzip/poc6/hexinator.pdf">hexinator.pdf</a></td>
<td><a href="/samples/pdfzip/poc6/eicar.zip">eicar.zip</a>&nbsp;(<a href="https://www.virustotal.com/#/file/2174e17e6b03bb398666c128e6ab0a27d4ad6f7d7922127fe828e07aa94ab79d/detection">scan virustotal.com</a>)</td>
<td>pdfzip</td>
<td><a href="/samples/pdfzip/poc6/polyglot.pdf">polyglot.pdf</a>&nbsp;(<a href="https://www.virustotal.com/#/file/883d08efc14e0cacc9a260d84fdef285b383cc9a9125366dfb0bf676ddeb0f98/detection">scan virustotal.com</a>)</td>
<td>PDF/ZIP polyglot with Eicar test in Zip - 2.9 Mo</td>
</tr>
</table>
</div>
<h2>Manual</h2>
<pre>
usage: truepolyglot format [options] output-file
Generate a polyglot file.
Formats availables:
* pdfzip: Generate a file valid as PDF and ZIP. The format is closest to PDF.
* zippdf: Generate a file valid as ZIP and PDF. The format is closest to ZIP.
* szippdf: Generate a file valid as ZIP and PDF. The format is strictly a ZIP. Archive is modified.
positional arguments:
{pdfzip,zippdf,szippdf}
Output polyglot format
output_file Output polyglot file path
optional arguments:
-h, --help show this help message and exit
--pdffile PDFFILE PDF input file
--zipfile ZIPFILE ZIP input file
--verbose {none,error,info,debug}
Verbosity level (default: debug)
TruePolyglot v1.3
</pre>
<h2>Code</h2>
<a href="https://git.hackade.org/truepolyglot.git/">Project Git repository</a>
<h2>Contact</h2>
On <a href="https://webchat.freenode.net/">IRC Freenode</a> my nickname is hackade or by mail at <a href="mailtp:truepolyglot@hackade.org">truepolyglot@hackade.org</a>.
</body>
</html>

2
website/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
website/start_server.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
echo "http://127.0.0.1:8000"
python -m SimpleHTTPServer 8000

2
website/update.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
rsync -av --progress ./ -e ssh dragon:/var/www/html/truepolyglot/