commit 726e9a98b4980e6f712b60af0d1a84b46fc6e516 Author: Sven Slootweg Date: Mon Mar 4 06:51:37 2013 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/b64reader.py b/b64reader.py new file mode 100644 index 0000000..50e451d --- /dev/null +++ b/b64reader.py @@ -0,0 +1,48 @@ +import base64, sys, math + +class Base64Reader(object): + def __init__(self, source): + self.source = source + self.buff = "" + self.done = False + + def read(self, size = -1): + if size < 0: + return base64.b64encode(self.source.read()) + else: + if self.done == False: + if len(self.buff) < size: + actual_size = int(math.ceil(size / 3) * 3) + data = self.source.read(actual_size) + + if data == "": + self.done = True + return self.buff + + # TODO: Investigate whether the possibility exists that we get the wrong amount + # of bytes from the source read. + self.buff += base64.b64encode(data) + + if len(self.buff) > size: + returndata = self.buff[:size] + self.buff = self.buff[size:] + else: + returndata = self.buff + self.buff = "" + + return returndata + else: + returndata = self.buff[:size] + self.buff = self.buff[size:] + return returndata + else: + return "" + + def flush(self): + pass + + def write(self, data): + pass + + def close(self): + self.source.close() diff --git a/gzipreader.py b/gzipreader.py new file mode 100644 index 0000000..ce53fc0 --- /dev/null +++ b/gzipreader.py @@ -0,0 +1,47 @@ +import zlib, sys + +class GzipReader(object): + source = None + cobj = None + done = False + buff = "" + + def __init__(self, source): + self.source = source + self.cobj = zlib.compressobj() + + def read(self, size = -1): + if self.done == False: + if size < 0: + data = self.source.read() + return self.cobj.compress(data) + self.cobj.flush(zlib.Z_FINISH) + else: + # Keep reading and compressing until we have something to return. + while len(self.buff) < size: + data = self.source.read(size) + + if data == "": + # Process the last data left in the compressor buffer. + self.buff += self.cobj.flush(zlib.Z_FINISH) + + # Mark as done to prevent calling flush(zlib.Z_FINISH) twice. + self.done = True + + return self.buff + + self.buff += self.cobj.compress(data) + + returndata = self.buff[:size] + self.buff = self.buff[size:] + return returndata + else: + return "" + + def flush(self): + pass + + def write(self, data): + pass + + def close(self): + self.source.close() diff --git a/pysfx b/pysfx new file mode 100644 index 0000000..3d7635a --- /dev/null +++ b/pysfx @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +import zlib, base64, sys, argparse, os +from gzipreader import GzipReader +from b64reader import Base64Reader + +parser = argparse.ArgumentParser(description="Creates an SFX from a specified archive or file.") +parser.add_argument("-a", help="Treat the input file as a tar.gz archive that needs to be extracted upon running the SFX.", action="store_true", dest="is_archive") +parser.add_argument("-s", help="Define a command to be run after extraction of the SFX. %%NAME will be replaced with the path of the extracted file or folder. " + "For archives, the working directory is set to the extraction directory.", action="store", dest="command") +parser.add_argument("input_file", metavar="INPUTFILE", type=str, nargs=1, help="The file to read from. Use a dash (-) to read from STDIN instead.") +parser.add_argument("output_file", metavar="OUTPUTFILE", type=str, nargs=1, help="The file to write to. Use a dash (-) to write to STDOUT instead.") +options = vars(parser.parse_args()) + +if options['input_file'][0] == "-": + infile = sys.stdin + extension = "dat" +else: + infile = open(options['input_file'][0], "rb") + extension = os.path.splitext(options['input_file'][0]) + +if options['output_file'][0] == "-": + outfile = sys.stdout +else: + outfile = open(options['output_file'][0], "wb") + +if options['is_archive'] == True: + is_archive = "True" + extension = "tar.gz" +else: + is_archive = "False" + +if options['command']: + run_after_extract = "True" + command = options['command'] +else: + run_after_extract = "False" + command = "" + +template = open("%s/unpack.template" % os.path.dirname(__file__), "r") + +variables = { + "run_after_extract": run_after_extract, + "targz": is_archive, + "extension": extension, + "command": command +} + +for curline in template: + if curline.startswith('"""EOFDATA'): + # Found the EOF data marker, insert packed data before + # moving on with the next line. + outfile.write(curline) + + data = b"" + reader = Base64Reader(GzipReader(infile)) + chunk_size = 128 + + while True: + chunk = reader.read(chunk_size) + + if chunk == "": + break + + outfile.write(chunk + "\n") + else: + if "{%" in curline: + for variable_key, variable_value in variables.iteritems(): + curline = curline.replace("{%%%s}" % variable_key, variable_value) + + outfile.write(curline) + +outfile.close() diff --git a/unpack.template b/unpack.template new file mode 100644 index 0000000..f25de8f --- /dev/null +++ b/unpack.template @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +import zlib, base64, sys, os, random, shlex, subprocess + +run_after_extract = {%run_after_extract} +targz = {%targz} +extension = "{%extension}" +command = "{%command}" + +try: + if sys.argv[1] != "-q": + quiet = True + else: + quiet = False +except IndexError: + quiet = False + +if quiet == False: + sys.stdout.write("PySFX 1.0 by Sven Slootweg http://cryto.net/pysfx\n") + sys.stdout.write("PySFX may be reused, modified, and redistributed freely without restriction under the WTFPL.\n\n") + +identifier = "pysfx-%s" % "".join(["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[random.randint(0, 61)] for i in xrange(0, 16)]) +directory_destination = "/var/tmp/%s" % identifier +file_destination = "/var/tmp/%s.%s" % (identifier, extension) + +if targz == True: + name = "/var/tmp/%s" +else: + name = "/var/tmp/%s.%s" % (identifier, extension) + +reader = open(__file__, "rb") +reading_data = False + +writer = open(file_destination, "wb") + +dobj = zlib.decompressobj() + +total_bytes = 0 +original_bytes = 0 + +for line in reader: + if line.startswith('"""'): + reading_data = False + + if reading_data == True: + data = dobj.decompress(base64.b64decode(line.rstrip("\r\n"))) + writer.write(data) + total_bytes += (len(line) - 1) + original_bytes += len(data) + + if line.startswith('"""EOFDATA'): + reading_data = True + +writer.write(dobj.flush()) +writer.close() + +reader.close() + +if quiet == False: + sys.stdout.write("Processed %d bytes, of which %d bytes were written to %s.\n" % (total_bytes, original_bytes, file_destination)) + +if targz == True: + stfu = open(os.devnull, 'w') + + if quiet == False: + sys.stdout.write("Unpacking archive...\n") + + os.makedirs(directory_destination) + + result = subprocess.call(["tar", "-xzf", file_destination, "-C", directory_destination], stdout=stfu, stderr=stfu) + + if result != 0: + sys.stderr.write("Extraction of inner archive failed. The file may be corrupted.\n") + exit(1) + +if run_after_extract == True: + tokens = shlex.split(command) + result = subprocess.call(tokens, cwd=directory_destination) + + if result != 0: + sys.stderr.write("Autorun command failed. The file may be corrupted.\n") + exit(1) + +"""EOFDATA +"""