#!/usr/bin/env python3
# ldifunwrap -- unwrap and optionally Base64-decode values within LDIF files
import argparse
import base64
import re
import sys

def univis(string, color=False):
    format = "\033[1;37;41m%s\033[m" if color else "%s"

    return re.sub(r"[\x00-\x1f]",
                  lambda m: format % ("<U+%04X>" % ord(m.group(0))),
                  string)

def binvis(buf, color=False):
    format = b"\033[1;31m%s\033[m" if color else b"%s"

    return re.sub(rb"[\x00-\x1f\x7f-\xff]",
                  lambda m: format % (b"<%02X>" % m.group(0)[0]),
                  buf).decode()

def isprintable(string):
    # Doesn't block weird Unicode, but does block ASCII newlines.
    return not any([x < 0x20 for x in map(ord, string)])

def unfold(lines):
    buf = None
    for line in lines:
        line = line.rstrip("\r\n")
        if line.startswith(" "):
            if not buf:
                raise ValueError("leading continuation line")
            buf += line[1:]
        else:
            if buf is not None:
                yield buf
            buf = line
    if buf is not None:
        yield buf

parser = argparse.ArgumentParser()
parser.add_argument("-d", "--decode", action="store_true",
                        help="Base64-decode fields that are valid UTF-8 text")
parser.add_argument("-v", "--visualize", action="store_true",
                        help="visualize unprintable characters")
args = parser.parse_args()

color = sys.stdout.isatty()

for line in unfold(sys.stdin):
    if not line:
        print()
    elif line.startswith("#"):
        print(line)
    elif (": " in line) and args.decode:
        k, v = line.split(": ", 1)
        if k.endswith(":"):
            vb = base64.b64decode(v)
            # With visualize=False, always output valid LDIF, leaving fields
            # Base64-encoded if they wouldn't result in printable ASCII.
            #
            # With visualize=True, output pseudo-LDIF with ANSI highlighting
            # for visualization.
            try:
                vn = vb.decode()
            except UnicodeDecodeError:
                if args.visualize:
                    k = k[:-1]
                    v = binvis(vb, color=color)
            else:
                if args.visualize:
                    k = k[:-1]
                    v = univis(vn, color=color)
                elif isprintable(vn):
                    k = k[:-1]
                    v = vn
        line = "%s: %s" % (k, v)
        print(line)
    else:
        print(line)
