#!/usr/bin/python
#-*- coding:utf-8 -*-

"""
xor strings
options:
    -s  -  string with \\xAF escapes
    -r  -  raw string
    -h  -  hex-encoded string (non-letterdigit chars are stripped)
    -f  -  read data from file (- for stdin)
    -n  -  no newline at the end
    --no-cycle / --nc  -  pad smaller strings with null bytes
example: xor -s lol -h 414243 -f /etc/passwd

author: hellman ( hellman1908@gmail.com )
"""

from __future__ import print_function
import sys
import string
import getopt


DATA_OPTS = "s:r:h:f:"
HEXES = set("0123456789abcdefABCDEF")


def main():
    nocycle = False
    nonewline = False
    try:
        opts, args = getopt.getopt(sys.argv[1:], "n" + DATA_OPTS, ["no-cycle", "nc"])
        datas = []
        for c, val in opts:
            if c in ("--no-cycle", "--nc"):
                nocycle = True
            elif c == "-n":
                nonewline = True
            else:
                v = arg_data(c, val)
                datas.append(v)
        if not datas:
            raise getopt.GetoptError("no data given")
    except getopt.GetoptError as e:
        print("error:", e, file=sys.stderr)
        print(__doc__, file=sys.stderr)
        quit()

    sys.stdout.buffer.write(xor(datas, nocycle=nocycle))
    if not nonewline:
        sys.stdout.buffer.write(b"\n")


def xor(args, nocycle=False):
    # Sort by len DESC
    args.sort(key=len, reverse=True)
    res = list(args.pop(0))
    maxlen = len(res)

    for s in args:
        slen = len(s)
        srange = range(slen if nocycle else maxlen)
        for i in srange:
            res[i] ^= s[i % slen]
    return bytes(res)


def from_str(s):
    res = []
    i = 0
    while True:
        if i + 4 > len(s):
            break

        if s[i] == "\\" and s[i+1] == "x" and s[i+2] in HEXES and s[i+3] in HEXES:
            res.append(int(s[i+2:i+4], 16))
            i += 4
        else:
            res.append(ord(s[i]))
            i += 1
    res += s[i:].encode("ascii")
    return bytes(res)

def from_file(s):
    if s == "-":
        return sys.stdin.buffer.read()
    return open(s, "rb").read()


def arg_data(opt, s):
    if opt == "-s":
        return from_str(s)
    elif opt == "-r":
        return str.encode(s)
    elif opt == "-h":
        return bytes.fromhex(s)
    elif opt == "-f":
        return from_file(s)
    raise getopt.GetoptError("unknown option -%s" % opt)


if __name__ == '__main__':
    main()
