Commit 9075d711 authored by Sophie Brun's avatar Sophie Brun

New upstream version 0.4.1

parent 6f3b2db1
Metadata-Version: 1.0
Name: peepdf
Version: 0.3.6
Version: 0.4.1
Summary: UNKNOWN
Home-page: http://eternal-todo.com
Author: Jose Miguel Esparza
Author-email: UNKNOWN
License: GNU GPLv3
Description-Content-Type: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Metadata-Version: 1.0
Name: peepdf
Version: 0.3.6
Version: 0.4.1
Summary: UNKNOWN
Home-page: http://eternal-todo.com
Author: Jose Miguel Esparza
Author-email: UNKNOWN
License: GNU GPLv3
Description-Content-Type: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
......@@ -5,12 +5,10 @@ peepdf/PDFConsole.py
peepdf/PDFCore.py
peepdf/PDFCrypto.py
peepdf/PDFFilters.py
peepdf/PDFOutput.py
peepdf/PDFUtils.py
peepdf/__init__.py
peepdf/aes.py
peepdf/ccitt.py
peepdf/constants.py
peepdf/jjdecode.py
peepdf/lzw.py
peepdf/main.py
......
jsbeautifier==1.6.2
colorama==0.3.7
future>=0.16.0
Pillow==3.2.0
pythonaes==1.0
......@@ -58,8 +58,8 @@ preDefinedCode = 'var app = this;'
# Regex that matches any character that's <32 && >127 and not a whitespace.
bad_chars_re = "|".join(re.escape(chr(ch)) for ch in (
[ch for ch in xrange(32) if chr(ch) not in "\n\r\t\f"] +
[ch for ch in xrange(128, 256)]
[ch for ch in range(32) if chr(ch) not in "\n\r\t\f"] +
[ch for ch in range(128, 256)]
))
def analyseJS(code, context=None, manualAnalysis=False):
......@@ -199,8 +199,8 @@ def isJavascript(content):
if re.findall(reJSscript, content, re.DOTALL | re.IGNORECASE):
return True
_, count = re.subn(bad_chars_re, "", content, len(content) / 10)
if count == len(content) / 10:
_, count = re.subn(bad_chars_re, "", content, len(content) // 10)
if int(count) == len(content) // 10:
return False
for string in jsStrings:
......@@ -244,7 +244,7 @@ def searchObfuscatedFunctions(jsCode, function):
return obfuscatedFunctionsInfo
def unescape(escapedBytes, unicode=True):
def unescape(escapedBytes, str=True):
'''
This method unescapes the given string
......@@ -253,13 +253,13 @@ def unescape(escapedBytes, unicode=True):
'''
# TODO: modify to accept a list of escaped strings?
unescapedBytes = ''
if unicode:
if str:
unicodePadding = '\x00'
else:
unicodePadding = ''
try:
if escapedBytes.lower().find('%u') != -1 or escapedBytes.lower().find('\u') != -1 or escapedBytes.find('%') != -1:
if escapedBytes.lower().find('\u') != -1:
if escapedBytes.lower().find('%u') != -1 or escapedBytes.lower().find('\\u') != -1 or escapedBytes.find('%') != -1:
if escapedBytes.lower().find('\\u') != -1:
splitBytes = escapedBytes.split('\\')
else:
splitBytes = escapedBytes.split('%')
......
This diff is collapsed.
This diff is collapsed.
......@@ -26,15 +26,17 @@
'''
import hashlib
import itertools
import struct
import random
import warnings
import sys
import peepdf.aes
from itertools import cycle, izip
import six
warnings.filterwarnings("ignore")
paddingString = '\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A'
paddingString = b'\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A'
def computeEncryptionKey(password, dictOwnerPass, dictUserPass, dictOE, dictUE, fileID, pElement, dictKeyLength=128, revision=3, encryptMetadata=False, passwordType=None):
......@@ -62,7 +64,7 @@ def computeEncryptionKey(password, dictOwnerPass, dictUserPass, dictOE, dictUE,
password = password[:32]
elif lenPass < 32:
password += paddingString[:32-lenPass]
md5input = password + dictOwnerPass + struct.pack('<i', int(pElement)) + fileID
md5input = password + dictOwnerPass + struct.pack(b'<i', int(pElement)) + fileID
if revision > 3 and not encryptMetadata:
md5input += '\xFF'*4
key = hashlib.md5(md5input).digest()
......@@ -305,7 +307,10 @@ def RC4(data, key):
# Initialization
for x in range(256):
hash[x] = ord(key[x % keyLength])
if six.PY3:
hash[x] = ord(chr(key[x % keyLength]))
else:
hash[x] = ord(key[x % keyLength])
box[x] = x
for x in range(256):
y = (y + int(box[x]) + int(hash[x])) % 256
......@@ -321,7 +326,10 @@ def RC4(data, key):
box[z] = box[y]
box[y] = tmp
k = box[((box[z] + box[y]) % 256)]
ret += chr(ord(data[x]) ^ k)
if six.PY3:
ret += chr(data[x] ^ k)
else:
ret += chr(ord(data[x]) ^ k)
return ret
......@@ -336,5 +344,5 @@ def xor(bytes, key):
@param key: Key used for the operation, it's cycled.
@return: The xored bytes
'''
key = cycle(key)
return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in izip(bytes, key))
key = itertools.cycle(key)
return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(bytes, key))
......@@ -58,8 +58,10 @@
import zlib
import struct
import six
import peepdf.lzw
from peepdf.PDFUtils import getNumsFromBytes, getBytesFromBits, getBitsFromNum
from peepdf.ccitt import CCITTFax
......@@ -263,8 +265,13 @@ def flateDecode(stream, parameters):
'''
decodedStream = ''
try:
decodedStream = zlib.decompress(stream)
except:
if six.PY3:
decodedStream = zlib.decompress(stream.encode('latin-1'))
decodedStream = decodedStream.decode('latin-1')
else:
decodedStream = zlib.decompress(stream)
except Exception as a:
return (-1, 'Error decompressing string')
if parameters is None or parameters == {}:
......@@ -469,7 +476,7 @@ def pre_prediction(stream, predictor, columns, colors, bits):
# PNG prediction
if predictor >= 10 and predictor <= 15:
# PNG prediction can vary from row to row
for row in xrange(len(stream) / columns):
for row in range(len(stream) / columns):
rowdata = [ord(x) for x in stream[(row * columns):((row + 1) * columns)]]
filterByte = predictor - 10
rowdata = [filterByte] + rowdata
......@@ -537,7 +544,7 @@ def post_prediction(decodedStream, predictor, columns, colors, bits):
numSamplesPerRow = columns + 1
bytesPerSample = (colors * bits + 7) / 8
upRowdata = (0,) * numSamplesPerRow
for row in xrange(numRows):
for row in range(numRows):
rowdata = [ord(x) for x in decodedStream[(row * bytesPerRow):((row + 1) * bytesPerRow)]]
# PNG prediction can vary from row to row
filterByte = rowdata[0]
......@@ -787,12 +794,12 @@ def dctDecode(stream, parameters):
decodedStream = ''
try:
from PIL import Image
import StringIO
import io
except:
return (-1, 'Python Imaging Library (PIL) not installed')
# Quick implementation, assuming the library can detect the parameters
try:
im = Image.open(StringIO.StringIO(stream))
im = Image.open(io.StringIO(stream))
decodedStream = im.tostring()
return (0, decodedStream)
except:
......
This diff is collapsed.
......@@ -27,10 +27,7 @@
import os
import re
import htmlentitydefs
import json
import urllib
import urllib2
import html.entities
def clearScreen():
'''
......@@ -366,15 +363,15 @@ def unescapeHTMLEntities(text):
# character reference
try:
if text[:3] == "&#x":
return unichr(int(text[3:-1], 16))
return chr(int(text[3:-1], 16))
else:
return unichr(int(text[2:-1]))
return chr(int(text[2:-1]))
except ValueError:
pass
else:
# named entity
try:
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
text = chr(html.entities.name2codepoint[text[1:-1]])
except KeyError:
pass
return text # leave as is
......@@ -431,9 +428,9 @@ def vtcheck(md5, vtKey):
vtUrl = 'https://www.virustotal.com/vtapi/v2/file/report'
parameters = {'resource': md5, 'apikey': vtKey}
try:
data = urllib.urlencode(parameters)
req = urllib2.Request(vtUrl, data)
response = urllib2.urlopen(req)
data = urllib.parse.urlencode(parameters)
req = urllib.request.Request(vtUrl, data)
response = urllib.request.urlopen(req)
jsonResponse = response.read()
except:
return (-1, 'The request to VirusTotal has not been successful')
......
......@@ -41,8 +41,8 @@ def decryptData(data, password = None, keyLength = None, mode = 'CBC'):
if keyLength not in [128, 192, 256]:
return (-1, 'Bad length key in AES decryption process')
iv = map(ord, data[:16])
key = map(ord, password)
iv = list(map(ord, data[:16]))
key = list(map(ord, password))
data = data[16:]
if len(data) % 16 != 0:
data = data[:-(len(data)%16)]
......@@ -53,7 +53,7 @@ def decryptData(data, password = None, keyLength = None, mode = 'CBC'):
aesMode = cbc_mode.CBCMode(aesCipher, 16)
aesMode.set_iv(iv)
for i in range(0,len(data),16):
ciphertext = map(ord,data[i:i+16])
ciphertext = list(map(ord,data[i:i+16]))
decryptedBytes = aesMode.decrypt_block(ciphertext)
for byte in decryptedBytes:
decryptedData += chr(byte)
......
......@@ -36,7 +36,7 @@ class BitWriter(object):
"""
"""
if not ( length >= 0 and (1 << length) > data ):
raise BitWriterException, "Invalid data length"
raise BitWriterException("Invalid data length")
if length == 8 and not self._last_byte and self._bit_ptr == 0:
self._data += chr(data)
......@@ -108,7 +108,7 @@ class BitReader(object):
"""
"""
if bits > self.size:
raise BitReaderException, "Pointer position out of data"
raise BitReaderException("Pointer position out of data")
pbyte = bits >> 3
pbit = bits - (pbyte <<3)
......@@ -118,9 +118,9 @@ class BitReader(object):
"""
"""
if length <= 0:
raise BitReaderException, "Invalid read length"
raise BitReaderException("Invalid read length")
elif ( self.pos + length ) > self.size:
raise BitReaderException, "Insufficient data"
raise BitReaderException("Insufficient data")
n = 0
byte_ptr, bit_ptr = self._byte_ptr, self._bit_ptr
......@@ -228,7 +228,7 @@ class CCITTFax(object):
63 : codeword('00110100')
}
WHITE_TERMINAL_DECODE_TABLE = dict( (v, k) for k, v in WHITE_TERMINAL_ENCODE_TABLE.iteritems() )
WHITE_TERMINAL_DECODE_TABLE = dict( (v, k) for k, v in WHITE_TERMINAL_ENCODE_TABLE.items() )
BLACK_TERMINAL_ENCODE_TABLE = {
0 : codeword('0000110111'),
......@@ -297,7 +297,7 @@ class CCITTFax(object):
63 : codeword('000001100111')
}
BLACK_TERMINAL_DECODE_TABLE = dict( (v, k) for k, v in BLACK_TERMINAL_ENCODE_TABLE.iteritems() )
BLACK_TERMINAL_DECODE_TABLE = dict( (v, k) for k, v in BLACK_TERMINAL_ENCODE_TABLE.items() )
WHITE_CONFIGURATION_ENCODE_TABLE = {
64 : codeword('11011'),
......@@ -343,7 +343,7 @@ class CCITTFax(object):
2560 : codeword('000000011111')
}
WHITE_CONFIGURATION_DECODE_TABLE = dict( (v, k) for k, v in WHITE_CONFIGURATION_ENCODE_TABLE.iteritems() )
WHITE_CONFIGURATION_DECODE_TABLE = dict( (v, k) for k, v in WHITE_CONFIGURATION_ENCODE_TABLE.items() )
BLACK_CONFIGURATION_ENCODE_TABLE = {
64 : codeword('0000001111'),
......@@ -389,7 +389,7 @@ class CCITTFax(object):
2560 : codeword('000000011111')
}
BLACK_CONFIGURATION_DECODE_TABLE = dict( (v, k) for k, v in BLACK_CONFIGURATION_ENCODE_TABLE.iteritems() )
BLACK_CONFIGURATION_DECODE_TABLE = dict( (v, k) for k, v in BLACK_CONFIGURATION_ENCODE_TABLE.items() )
def __init__(self, ):
"""
......@@ -422,7 +422,7 @@ class CCITTFax(object):
if bitr.peek(self.EOL[1]) != self.EOL[0]:
if eol:
raise Exception, "No end-of-line pattern found (at bit pos %d/%d)" % (bitr.pos, bitr.size)
raise Exception("No end-of-line pattern found (at bit pos %d/%d)" % (bitr.pos, bitr.size))
else:
bitr.pos += self.EOL[1]
......@@ -433,11 +433,11 @@ class CCITTFax(object):
else:
bit_length = self.get_black_bits(bitr)
if bit_length == None:
raise Exception, "Unfinished line (at bit pos %d/%d), %s" % (bitr.pos, bitr.size, bitw.data)
raise Exception("Unfinished line (at bit pos %d/%d), %s" % (bitr.pos, bitr.size, bitw.data))
line_length += bit_length
if line_length > columns:
raise Exception, "Line is too long (at bit pos %d/%d)" % (bitr.pos, bitr.size)
raise Exception("Line is too long (at bit pos %d/%d)" % (bitr.pos, bitr.size))
bitw.write( (current_color << bit_length) - current_color, bit_length )
......@@ -465,7 +465,7 @@ class CCITTFax(object):
while check_conf:
check_conf = False
for i in xrange(2, 14):
for i in range(2, 14):
codeword = bitr.peek(i)
config_value = config_words.get((codeword, i), None)
......@@ -476,7 +476,7 @@ class CCITTFax(object):
check_conf = True
break
for i in xrange(2, 14):
for i in range(2, 14):
codeword = bitr.peek(i)
term_value = term_words.get((codeword, i), None)
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Here is defined info about the project, its author
and some constants to customize
"""
import os
import sys
PEEPDF_VERSION = '0.3'
PEEPDF_REVISION = '275'
AUTHOR = 'Jose Miguel Esparza'
AUTHOR_EMAIL = 'peepdf AT eternal-todo(dot)com'
AUTHOR_TWITTER = 'http://twitter.com/EternalTodo'
LICENCE = "GNU GPLv3"
PEEPDF_URL = 'http://peepdf.eternal-todo.com'
GITHUB_URL = 'https://github.com/jesparza/peepdf'
TWITTER_URL = 'http://twitter.com/peepdf'
PEEPDF_ROOT = os.path.dirname(
os.path.realpath(os.path.join(sys.argv[0], ".."))
)
ERROR_FILE = os.path.join(PEEPDF_ROOT, "errors.txt")
......@@ -188,10 +188,10 @@ class ByteDecoder(object):
iterator over the uncompressed bytes. Dual of
L{ByteEncoder.encodetobytes}. See L{ByteEncoder} for an
example of use.
"""
"""
codepoints = self._unpacker.unpack(bytesource)
clearbytes = self._decoder.decode(codepoints)
return clearbytes
......@@ -262,7 +262,7 @@ class BitPacker(object):
if pt == END_OF_INFO_CODE:
while len(tailbits) % 8:
tailbits.append(0)
if pt in [ CLEAR_CODE, END_OF_INFO_CODE ]:
nextwidth = minwidth
codesize = self._initial_code_size
......@@ -277,13 +277,13 @@ class BitPacker(object):
tailbits = tailbits[8:]
if tailbits:
tail = bitstobytes(tailbits)
for bt in tail:
yield struct.pack("B", bt)
class BitUnpacker(object):
......@@ -325,7 +325,7 @@ class BitUnpacker(object):
bits = []
offset = 0
ignore = 0
codesize = self._initial_code_size
minwidth = 8
while (1 << minwidth) < codesize:
......@@ -484,7 +484,7 @@ class Encoder(object):
self._max_code_size = max_code_size
self._buffer = ''
self._clear_codes()
self._clear_codes()
if max_code_size < self.code_size():
raise ValueError("Max code size too small, (must be at least {0})".format(self.code_size()))
......@@ -509,12 +509,12 @@ class Encoder(object):
if self._buffer:
yield self._prefixes[ self._buffer ]
self._buffer = ''
self._buffer = ''
yield CLEAR_CODE
self._clear_codes()
def encode(self, bytesource):
......@@ -527,7 +527,7 @@ class Encoder(object):
>>> enc = lzw.Encoder()
>>> [ cp for cp in enc.encode("gabba gabba yo gabba") ]
[103, 97, 98, 98, 97, 32, 258, 260, 262, 121, 111, 263, 259, 261, 256]
Modified by Jose Miguel Esparza to add support for PDF files encoding
"""
yield CLEAR_CODE
......@@ -551,7 +551,7 @@ class Encoder(object):
# want to call this.
new_prefix = self._buffer
if new_prefix + byte in self._prefixes:
new_prefix = new_prefix + byte
elif new_prefix:
......@@ -560,7 +560,7 @@ class Encoder(object):
new_prefix = byte
yield encoded
self._buffer = new_prefix
......@@ -604,7 +604,7 @@ class PagingEncoder(object):
>>> import peepdf.lzw
>>> enc = lzw.PagingEncoder(257, 2**12)
>>> coded = enc.encodepages([ "say hammer yo hammer mc hammer go hammer",
>>> coded = enc.encodepages([ "say hammer yo hammer mc hammer go hammer",
... "and the rest can go and play",
... "can't touch this" ])
...
......@@ -622,11 +622,11 @@ class PagingEncoder(object):
packer = BitPacker(initial_code_size=encoder.code_size())
packed = packer.pack(codes_and_eoi)
for byte in packed:
for byte in packed:
yield byte
class PagingDecoder(object):
"""
......@@ -646,7 +646,7 @@ class PagingDecoder(object):
try:
while 1:
cp = codepoints.next()
cp = next(codepoints)
if cp != END_OF_INFO_CODE:
yield cp
else:
......@@ -655,7 +655,7 @@ class PagingDecoder(object):
except StopIteration:
pass
def decodepages(self, bytesource):
"""
......@@ -734,7 +734,7 @@ def filebytes(fileobj, buffersize=1024):
for byte in buff: yield byte
buff = fileobj.read(buffersize)
def readbytes(filename, buffersize=1024):
"""
Opens a file named by filename and iterates over the L{filebytes}
......@@ -791,7 +791,7 @@ def intfrombits(bits):
Given a list of boolean values, interprets them as a binary
encoded, MSB-first unsigned integer (with True == 1 and False
== 0) and returns the result.
>>> import peepdf.lzw
>>> lzw.intfrombits([ 1, 0, 0, 1, 1, 0, 0, 0, 0 ])
304
......@@ -799,7 +799,7 @@ def intfrombits(bits):
ret = 0
lsb_first = [ b for b in bits ]
lsb_first.reverse()
for bit_index in range(len(lsb_first)):
if lsb_first[ bit_index ]:
ret = ret | (1 << bit_index)
......@@ -811,7 +811,7 @@ def bytestobits(bytesource):
"""
Breaks a given iterable of bytes into an iterable of boolean
values representing those bytes as unsigned integers.
>>> import peepdf.lzw
>>> [ x for x in lzw.bytestobits(b"\\x01\\x30") ]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0]
......@@ -858,7 +858,7 @@ def bitstobytes(bits):
if nextbit < 7: ret.append(nextbyte)
return ret
......@@ -871,15 +871,12 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''
import sys
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from io import StringIO
## LZWDecoder
##
......@@ -923,7 +920,7 @@ class LZWDecoder(object):
def feed(self, code):
x = ''
if code == 256:
self.table = [ chr(c) for c in xrange(256) ] # 0-255
self.table = [ chr(c) for c in range(256) ] # 0-255
self.table.append(None) # 256
self.table.append(None) # 257
self.prevbuf = ''
......@@ -958,8 +955,8 @@ class LZWDecoder(object):
x = self.feed(code)
yield x
if self.debug:
print >>sys.stderr, ('nbits=%d, code=%d, output=%r, table=%r' %
(self.nbits, code, x, self.table[258:]))
x = 'nbits=%d, code=%d, output=%r, table=%r' % self.nbits, code, x, self.table[258:]
sys.stderr.write(x)
return
......
......@@ -391,7 +391,7 @@ def main():
resetColor = Style.RESET_ALL
if options.version:
print peepdfHeader
print(peepdfHeader)
else:
if len(args) == 1:
fileName = args[0]
......@@ -464,7 +464,7 @@ def main():
traceback.print_exc(file=open(errorsFile, 'a'))
raise Exception('PeepException', 'Send me an email ;)')
elif options.commands is not None:
from PDFConsole import PDFConsole
from .PDFConsole import PDFConsole
console = PDFConsole(pdf, VT_KEY, options.avoidColors)
try:
......@@ -649,7 +649,7 @@ def main():
stats += '\t\t' + url + newLine
stats += newLine * 2
if fileName is not None:
print stats
print(stats)
if options.isInteractive:
from peepdf.PDFConsole import PDFConsole
......@@ -661,7 +661,7 @@ def main():
sys.exit()
except:
errorMessage = '*** Error: Exception not handled using the interactive console!! Please, report it to the author!!'
print errorColor + errorMessage + resetColor + newLine
print(errorColor + errorMessage + resetColor + newLine)
traceback.print_exc(file=open(errorsFile, 'a'))
except Exception as e:
if len(e.args) == 2:
......@@ -671,7 +671,7 @@ def main():
if excName is None or excName != 'PeepException':
errorMessage = '*** Error: Exception not handled!!'
traceback.print_exc(file=open(errorsFile, 'a'))
print errorColor + errorMessage + resetColor + newLine
print(errorColor + errorMessage + resetColor + newLine)
finally:
if os.path.exists(errorsFile):
message = newLine + 'Please, don\'t forget to report errors if found:' + newLine * 2
......
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
......@@ -2,13 +2,14 @@ from setuptools import setup
setup(
name="peepdf",
version="0.3.6",
version="0.4.1",
author="Jose Miguel Esparza",
license="GNU GPLv3",
url="http://eternal-todo.com",
install_requires=[
"jsbeautifier==1.6.2",
"colorama==0.3.7",
"future>=0.16.0",
"Pillow==3.2.0",
"pythonaes==1.0",
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment