|
|
#!/usr/bin/python3
|
|
|
|
|
|
""" xit
|
|
|
|
|
|
Usage:
|
|
|
xit (<FILE>)... [--size <SIZE>] [--seed <SEED>] [--overwrite] [--help]
|
|
|
|
|
|
XOR(in files) == XOR(out files)
|
|
|
|
|
|
Use cases:
|
|
|
$ xit A ^ A1 A2 # splits the secret A into two chuncks A1 and A2
|
|
|
$ xit A1 A2 ^ A # gets the secret back, turns A1 and A2 into A
|
|
|
$ xit ^ R1 R2 R3 # generates random files R1, R2, and R3
|
|
|
|
|
|
The files at the LEFT of ^ are the INput files.
|
|
|
The files at the RIGHT of ^ are the OUTput files.
|
|
|
|
|
|
Special cases:
|
|
|
$ xit A1 A2 ^ B1 B2 # A1 xor A2 == B1 xor B2
|
|
|
$ xit C ^ B A-{3} # generates 4 files: A1, A-|1|, A-|2|, A-|3|
|
|
|
# So, C == B ^ A-|1| ^ A-|2| ^ A-|3|
|
|
|
$ xit C ^ B A-{2} B-{2} # generates 5 files:
|
|
|
# So, C == B ^ A-|1| ^ A-|2|
|
|
|
# AND: C == B ^ B-|1| ^ B-|2|
|
|
|
|
|
|
|x| means the sha1 digest, included in the file name, of the file itself.
|
|
|
|
|
|
Arguments:
|
|
|
size 'even': assumes all input files are of the same size [default]
|
|
|
'min': keeps the smallest size, truncate others if needed
|
|
|
'max': keeps the biggest size, pad others if needed
|
|
|
integer: sets all files at the same size, truncating or padding
|
|
|
|
|
|
FILE A bunch of files, first the input files then the output files.
|
|
|
Separated by ^. Without input files, it generates random files.
|
|
|
|
|
|
input files Enclosed in underscores (_) means a string as a key
|
|
|
Otherwise, means a readable file
|
|
|
|
|
|
output files With one {nb} means a multiple output, more than one is an error
|
|
|
Otherwise, means a writable file
|
|
|
|
|
|
Options:
|
|
|
--size, -s SIZE input size: first, min, max, even [default: even], last or a size
|
|
|
--seed, -e SEED Uses a seed to generate test randomness, a zero triggers the
|
|
|
Python's internal Random instead of urandom
|
|
|
--overwrite, -w Allows overwritting of existing output files
|
|
|
"""
|
|
|
|
|
|
import sys, re, random
|
|
|
from docopt import docopt
|
|
|
from pathlib import Path
|
|
|
from collections import Counter
|
|
|
import lib_xit
|
|
|
import commands
|
|
|
import utils
|
|
|
|
|
|
if __name__ != '__main__': sys.exit()
|
|
|
|
|
|
def manageFileErrors(inArgs, outFiles, outSha1, overwrite, size):
|
|
|
errors = {
|
|
|
'Input files not found':
|
|
|
[a for a in inArgs if not Path(a).is_file()],
|
|
|
'Output files already exist (see --overwrite)':
|
|
|
[] if overwrite else [a for a in outFiles if Path(a).is_file()],
|
|
|
'Duplicate input files':
|
|
|
[f for f,count in
|
|
|
Counter([str(Path(a).resolve()) for a in inArgs]).items()
|
|
|
if count>1],
|
|
|
'Duplicate output files':
|
|
|
[f for f,count in Counter(outFiles).items() if count>1],
|
|
|
'Files both input AND output':
|
|
|
list(set(inArgs) & set(outFiles)),
|
|
|
'Input files have different sizes (see --size)':
|
|
|
[] if size!='even'
|
|
|
else len(set((Path(a).lstat().st_size for a in inArgs if Path(a).is_file()))) > 1 and inArgs,
|
|
|
'Some input files have null size':
|
|
|
[f for f in inArgs if Path(f).is_file() and Path(f).lstat().st_size==0],
|
|
|
}
|
|
|
return errors
|
|
|
|
|
|
arguments = docopt(__doc__)
|
|
|
files, overwrite, size, seed = (
|
|
|
arguments[a]
|
|
|
for a in ('<FILE>', '--overwrite', '--size', '--seed')
|
|
|
)
|
|
|
if seed: # This makes predictable outputs for random generators! ONLY usefull for test purpose!
|
|
|
if int(seed): random.seed(seed) # A zero just forces the use of Python's random instead of urandom
|
|
|
lib_xit.useUrandom = False
|
|
|
try:
|
|
|
try:
|
|
|
inArgs, outArgs = utils.splitList(files, '^')
|
|
|
except ValueError:
|
|
|
raise ValueError('Exactly one ^ required!')
|
|
|
if not outArgs:
|
|
|
raise ValueError('There must be at least an output after ^')
|
|
|
|
|
|
inFileArgs, inStrArgs = [], []
|
|
|
for i in inArgs:
|
|
|
m = utils.StrKey(i)
|
|
|
if m:
|
|
|
inStrArgs.append(str(m))
|
|
|
else:
|
|
|
inFileArgs.append(i)
|
|
|
|
|
|
outFiles, outSha1Files = [], []
|
|
|
for o in outArgs:
|
|
|
s = utils.Sha1File(o)
|
|
|
if s:
|
|
|
outSha1Files.append(s)
|
|
|
else:
|
|
|
outFiles.append(o)
|
|
|
|
|
|
# Prevents output file being the same as the input file
|
|
|
if len(inArgs)==1 : # Only one input, file. If there are two, it can be a simple xor.
|
|
|
if not len(outFiles): # Without normal file, all {1} sha1 file will be the same as the input file
|
|
|
for o in outSha1Files:
|
|
|
if int(o) == 1:
|
|
|
raise OSError('The output file will have the same content as the input file!')
|
|
|
elif len(outFiles)==1 and not outSha1Files: # One input file and one output file
|
|
|
raise OSError('The output file will have the same content as the input file!')
|
|
|
|
|
|
sizesAvailable = ('first', 'even', 'min', 'max', 'last')
|
|
|
size = size.lower()
|
|
|
if size not in sizesAvailable:
|
|
|
try:
|
|
|
size = utils.evalMultiple(size)
|
|
|
except ValueError:
|
|
|
raise ValueError('size is not an int and not in: '+', '.join(sizesAvailable))
|
|
|
if size <= 0:
|
|
|
raise ValueError('size must be strictly positive!')
|
|
|
|
|
|
assert type(size) is int or size in sizesAvailable
|
|
|
if outSha1Files: overwrite = True # As sha1 is known after the file checks...
|
|
|
errors = manageFileErrors(inFileArgs, outFiles, outSha1Files, overwrite, size)
|
|
|
if any(errors.values()):
|
|
|
for error, files in errors.items():
|
|
|
if files:
|
|
|
sys.stderr.write(error+':\n')
|
|
|
for e in files: sys.stderr.write(' '+e+'\n')
|
|
|
|
|
|
commands.xorFiles(inStrArgs, inFileArgs, outFiles, outSha1Files, size)
|
|
|
except (OSError, ValueError, IndexError) as e:
|
|
|
sys.exit(e)
|
|
|
|