You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
3.4 KiB
150 lines
3.4 KiB
6 months ago
|
#!/usr/bin/env python
|
||
|
# encoding: utf-8
|
||
|
|
||
|
""" Python Code Protector """
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import imp
|
||
|
import marshal
|
||
|
import traceback
|
||
|
|
||
|
import _pyprotect
|
||
|
|
||
|
MAGIC = imp.get_magic()
|
||
|
|
||
|
# recurse dir
|
||
|
m_recurse = True
|
||
|
|
||
|
# delete after protect
|
||
|
m_delete = False
|
||
|
|
||
|
# follow symlink
|
||
|
m_symlink = True
|
||
|
|
||
|
|
||
|
def wr_long(f, x):
|
||
|
"""Internal; write a 32-bit int to a file in little-endian order."""
|
||
|
f.write(chr(x & 0xff))
|
||
|
f.write(chr((x >> 8) & 0xff))
|
||
|
f.write(chr((x >> 16) & 0xff))
|
||
|
f.write(chr((x >> 24) & 0xff))
|
||
|
|
||
|
|
||
|
def protect_file(file, cfile=None):
|
||
|
""" Protect a pyfile """
|
||
|
if os.path.islink(file) and not m_symlink:
|
||
|
print 'Protect: ignore symlink file:', file
|
||
|
return True
|
||
|
|
||
|
if not os.path.isfile(file):
|
||
|
return False
|
||
|
|
||
|
if file.endswith(".pyc") or file.endswith(".pyo"):
|
||
|
return False
|
||
|
|
||
|
if not file.endswith(".py"):
|
||
|
print 'Protect: ignore non-py file:', file
|
||
|
return True
|
||
|
|
||
|
f = open(file, 'U')
|
||
|
try:
|
||
|
timestamp = long(os.fstat(f.fileno()).st_mtime)
|
||
|
except AttributeError:
|
||
|
timestamp = long(os.stat(file).st_mtime)
|
||
|
codestring = f.read()
|
||
|
f.close()
|
||
|
if codestring and codestring[-1] != '\n':
|
||
|
codestring = codestring + '\n'
|
||
|
|
||
|
try:
|
||
|
os.putenv('PYTHONOPTIMIZE', '2')
|
||
|
codeobject = compile(codestring, file, 'exec')
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
return False
|
||
|
|
||
|
_pyprotect.protect(codeobject)
|
||
|
|
||
|
if cfile is None:
|
||
|
cfile = file + (__debug__ and 'c' or 'o')
|
||
|
|
||
|
fc = open(cfile, 'wb')
|
||
|
fc.write('\0\0\0\0')
|
||
|
wr_long(fc, timestamp)
|
||
|
marshal.dump(codeobject, fc)
|
||
|
fc.flush()
|
||
|
fc.seek(0, 0)
|
||
|
fc.write(MAGIC)
|
||
|
fc.close()
|
||
|
|
||
|
if m_delete:
|
||
|
try:
|
||
|
os.unlink(file)
|
||
|
except Exception, e:
|
||
|
print 'Protect: failed to delete file: %s, reason: %s' % (file,
|
||
|
str(e))
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
def protect_dir(dirname):
|
||
|
""" Protect py files in a dir
|
||
|
|
||
|
"""
|
||
|
for dirname, dirnames, filenames in os.walk(dirname):
|
||
|
for filename in filenames:
|
||
|
fullname = os.path.join(dirname, filename)
|
||
|
protect_file(fullname)
|
||
|
|
||
|
if not m_recurse:
|
||
|
break
|
||
|
|
||
|
|
||
|
def protect(filenames):
|
||
|
""" Protect file or dirs """
|
||
|
|
||
|
for filename in filenames:
|
||
|
if os.path.isfile(filename):
|
||
|
protect_file(filename)
|
||
|
elif os.path.isdir(filename):
|
||
|
protect_dir(filename)
|
||
|
else:
|
||
|
print 'Unknown file type:', filename
|
||
|
|
||
|
|
||
|
def parse_cmdline():
|
||
|
""" Parse command line
|
||
|
|
||
|
"""
|
||
|
from optparse import OptionParser
|
||
|
global m_recurse, m_delete, m_symlink
|
||
|
|
||
|
parser = OptionParser(usage="usage: %prog [options]")
|
||
|
|
||
|
parser.add_option('-R', dest='recurse', default=None, action='store_true',
|
||
|
help='Protect all .py in the dir and subdirs')
|
||
|
parser.add_option('--delete', default=None, action='store_true',
|
||
|
help='Delete origin .py file after protect')
|
||
|
parser.add_option('--symlink', default=None, action='store_true',
|
||
|
help='Follow symlink file')
|
||
|
|
||
|
opts, args = parser.parse_args()
|
||
|
|
||
|
m_recurse = opts.recurse
|
||
|
m_delete = opts.delete
|
||
|
m_symlink = opts.symlink
|
||
|
|
||
|
return args
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
filenames = parse_cmdline()
|
||
|
|
||
|
if not filenames:
|
||
|
print 'Usage: pyprotect [-R] [--delete] file|dir'
|
||
|
sys.exit(0)
|
||
|
|
||
|
protect(filenames)
|
||
|
|