class StandardBrowser(Browser):
    def __init__(self, name=None, cmd=None):
        self.name = name if name else get_setting('default_browser')
        self.cmd = cmd

    def run(self, url, name=None):
        name = name if name else self.name
        if url:
            if '://' not in url:
                url = 'https://' + url
            try:
                cmd = self.cmd if self.cmd else get_setting('browsers')[name]
            except KeyError:
                raise PasswordError("unknown browser, choose from %s." % (
                    ', '.join(get_setting('browsers', name))
                ), culprit=name)

            try:
                cmd = cmd.format(url=url)
            except TypeError:
                pass
            except KeyError as e:
                raise PasswordError(
                    'unknown key {%s}.  Choose from: {url}.' % e.args[0],
                    culprit=setting_path('browsers', name)
                )

            browser = Cmd(cmd, 'sOE')
                # capture both stdout and stderr because browsers are
                # notoriously noisy
            log("running '%s'" % str(browser))
            browser.start()
        else:
            raise PasswordError('url not available from account.') class Collection(object):
    fmt = '{v}'     # default value format
    sep = " "       # default separator
    splitter = "|"  # default format splitter (goes between fmt and sep in template)

    """Collection

    Takes a list or dictionary and provides both a list like and dictionary like
    API, meaning that it provides the keys, values, and items methods like
    dictionary and you can iterate through the value like a list.  When applying
    keys to a list, the indices are returned as the keys. You can also use an
    index or a key to get a value, you test to see if a value is in the
    collection using the *in* operator, and you can get the length of the
    collection.

    If splitter is a string or None, then you can pass in a string and it will
    be split into a list to form the collection.  You can also pass in a
    splitting function.
    """

    def __init__(self, collection, splitter=None, **kwargs):
        if is_str(collection):
            if callable(splitter):
                self.collection = splitter(collection, **kwargs)
                return
            elif splitter is not False:
                self.collection = collection.split(splitter)
                return
        if is_collection(collection):
            self.collection = collection
            return
        if collection is None:
            self.collection = []
            return
        # is scalar
        self.collection = {None: collection}

    def keys(self):
        try:
            return list(self.collection.keys())
        except AttributeError:
            return list(range(len(self.collection)))

    def values(self):
        try:
            return [self.collection[k] for k in self.collection.keys()]
        except AttributeError:
            return list(self.collection)

    def items(self):
        try:
            return [(k, self.collection[k]) for k in self.collection.keys()]
        except AttributeError:
            return list(enumerate(self.collection))

    def get(self, key, default=Unspecified):
        try:
            return self.collection[key]
        except (KeyError, IndexError):
            if default == Unspecified:
                raise
            return default

    def render(self, fmt=None, sep=None):
        """Convert the collection into a string

        fmt (str):
            fmt is a format string applied to each of the items in the
            collection where {k} is replaced with the key and {v} replaced with
            the value.
        sep (str):
            The string used to join the formatted items.

        Example:

            >>> from collection import Collection

            >>> dogs = Collection({'collie': 3, 'beagle':1, 'shepherd': 2})
            >>> print('dogs: {}.'.format(dogs.render('{k} ({v})', ', ')))
            dogs: collie (3), beagle (1), shepherd (2).

            >>> print('dogs: {}.'.format(dogs.render(sep=', ')))
            dogs: 3, 1, 2.

        """
        if not fmt:
            fmt = self.fmt
        if not sep:
            sep = self.sep

        if callable(fmt):
            return sep.join(fmt(k, v) for k, v in self.items())
        return sep.join(fmt.format(v, k=k, v=v) for k, v in self.items())

    def __format__(self, template):
        """Convert the collection into a string

        The template consists of two components separated by a vertical bar. The
        first component specifies the formatting from each item. The key and
        value are interpolated using {{k}} to represent the key and {{v}} to
        represent the value.  The second component is the separator. Thus:

            >>> dogs = Collection({'collie': 3, 'beagle':1, 'shepherd': 2})
            >>> print('dogs: {:{{k}} ({{v}})|, }.'.format(dogs))
            dogs: collie (3), beagle (1), shepherd (2).

        """
        if template:
            components = template.split(self.splitter)
            if len(components) == 2:
                fmt, sep = components
            else:
                fmt, sep = components[0], " "
        else:
            fmt, sep = None, None
        if not fmt:
            fmt = self.fmt
        if not sep:
            sep = self.sep

        if callable(fmt):
            return sep.join(fmt(k, v) for k, v in self.items())
        return sep.join(fmt.format(v, k=k, v=v) for k, v in self.items())

    def __contains__(self, item):
        return item in self.values()

    def __iter__(self):
        return iter(self.values())

    def __len__(self):
        return len(self.collection)

    def __getitem__(self, key):
        return self.collection[key]

    def __repr__(self):
        return f'{self.__class__.__name__}({self.collection!r})'

    def __str__(self):
        return str(self.collection) def get_setting(name, default=None, expand=True):
    name = name.lower()
    try:
        value = Config[name]
    except KeyError:
        try:
            value = CONFIG_DEFAULTS[name]
        except KeyError:
            try:
                value = NONCONFIG_SETTINGS[name]
            except KeyError:
                return default
    if value is None:
        return default
    if name == 'gpg_ids':
        value = value.split() if is_str(value) else value
    elif name.endswith('_dir'):
        value = Path(value)
    elif expand and name.endswith('_file'):
        value = get_setting('settings_dir') / value
    return value class PasswordError(Error):
    """Password error.

    This exception subclasses
    `Inform.Error <https://github.com/KenKundert/inform#exception>`_.
    """
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs class ObscuredSecret(object):
    """Base class for obscured secrets."""

    # obscurers() {{{2
    @classmethod
    def obscurers(cls):
        for sub in sorted(cls.__subclasses__(), key=lambda s: s.__name__):
            if hasattr(sub, 'conceal') and hasattr(sub, 'reveal'):
                yield sub
                for each in sub.obscurers():
                    if hasattr(each, 'conceal') and hasattr(each, 'reveal'):
                        yield each

    # get_name() {{{2
    @classmethod
    def get_name(cls):
        try:
            return cls.NAME.lower()
        except AttributeError:
            # consider converting lower to upper case transitions in __name__ to
            # dashes.
            return cls.__name__.lower()

    # get_description() {{{2
    @classmethod
    def get_description(cls):
        return None

    # hide() {{{2
    @classmethod
    def hide(cls, text, encoding=None, decorate=False, symmetric=False, gpg_ids=None):
        encoding = encoding.lower() if encoding else 'base64'
        for obscurer in cls.obscurers():
            if encoding == obscurer.get_name():
                return obscurer.conceal(
                    text, decorate, symmetric=symmetric, gpg_ids=gpg_ids
                )
        raise PasswordError('not found.', culprit=encoding)

    # show() {{{2
    @classmethod
    def show(cls, text):
        match = DECORATED_LIST.match(text)
        if match:
            name = match.group(1)
            value = ''.join([s.strip('"' "'") for s in match.group(2).split()])

            for obscurer in cls.obscurers():
                if name == obscurer.__name__:
                    return obscurer.reveal(value)
            raise PasswordError('not found.', culprit=name)

        match = DECORATED_TEXT.match(text)
        if match:
            name = match.group(1)
            value = match.group(2)

            for obscurer in cls.obscurers():
                if name == obscurer.__name__:
                    return obscurer.reveal(value.strip('"' "'"))
            raise PasswordError('not found.', culprit=name)

        return Hidden.reveal(text)

    # encodings() {{{2
    @classmethod
    def encodings(cls):
        for c in cls.obscurers():
            yield c.get_name(), dedent(getattr(c, 'DESC', '')).strip()

    # default encoding() {{{2
    @classmethod
    def default_encoding(cls):
        return Hidden.NAME

    # __repr__() {{{2
    def __repr__(self):
        secret = ObscuredSecret.hide(self.plaintext, 'base64')
        if hasattr(self, 'is_secret') and not self.is_secret:
            return "Hidden('{}', is_secret=False)".format(secret)
        else:
            return "Hidden('{}')".format(secret)

    # __str__() {{{2
    def __str__(self):
        return self.render() HIDDEN_TOOL_FIELDS = 'NAME aliases default discovery browser default_url'.split() FORBIDDEN_TOOL_FIELDS = 'master_seed account_seed'.split() class Recognizer(object):
    def all_urls(self, components=False):
        urls = {}
        if hasattr(self, 'recognizers'):
            for each in self.recognizers:
                urls.update(each.all_urls(components))
        if hasattr(self, 'get_urls'):
            urls.update(self.get_urls(components))
        # returns a dictionary where the keys are the names associated with the
        # urls, and the value is a list of pairs, the first member of the pair
        # is a url and the second is a boolean indicating whether an exact match
        # is needed (use by cache).
        return urls

    def all_titles(self):
        titles = {}
        if hasattr(self, 'recognizers'):
            for each in self.recognizers:
                titles.update(each.all_titles())
        if hasattr(self, 'get_titles'):
            titles.update(self.get_titles())
        # returns a dictionary where the keys are the names associated with the
        # titles, and the value is a list of glob strings that match the title.
        return titles

    def get_name(self):
        return self.__class__.__name__

    def _inform_get_kwargs(self):
        kwargs = {}
        if hasattr(self, 'script'):
            kwargs['script'] = getattr(self, 'script')
            if kwargs['script'] is True:
                del kwargs['script']  # don't clutter arg list with a default
        if hasattr(self, 'name'):
            kwargs['name'] = getattr(self, 'name')
        return cull(kwargs)

    def render(self):
        return render(self) class Script:
    """Script

    Takes a string that contains attributes. Those attributes are expanded
    before being output. For example::

        Script('username: {username}, password: {passcode}')

    In this case, *{username}* and *{passcode}* are replaced by with the value
    of the corresponding account attribute. In addition to the account
    attributes, *{tab}* and *{return}* are replaced by a tab or carriage return
    character.

    Args:
        script (str):  The script.
    """
    SPLITTER = re.compile(r'({[\w. -]+})')

    def __init__(self, script='username: {username}, password: {passcode}'):
        self.script = script
        self.account = None
        self.rendered = None
        self.is_secret = None

    def initialize(self, account, field_name=None, field_key=None):
        self.account = account

    def __str__(self):
        if self.rendered is not None:
            return self.rendered
        self.is_secret = False
        components = []
        for cmd, val in self.components():
            components.append(val)
            if cmd == 'secret':
                self.is_secret = True
        return ''.join(components)

    def components(self, ask=False):
        """Iterates through the script.

        Yields a tuple for each component of a script. The tuple consists of the
        type of the component and the value of the component.  The type may be
        'tab' (a tab character), 'return' (a return character), 'text' (raw
        text), 'value' (the value of a field that is not a secret), 'sleep N' (a
        request to sleep N seconds), 'rate N' (set the autotype to 1 keystroke
        every N milliseconds), and finally a field name (the value of a
        field that is secret)..

        Args:
            ask (bool):
                Determines what happens when a composite field is encountered.
                If ask is true a dialog window is opened that that allows the
                user to select the desired member of the collection. If false, a
                PasswordError is raised.

        Raises:
            :exc:`avendesora.PasswordError`: attribute not found.
        """
        account = self.account
        for term in self.SPLITTER.split(self.script):
            if term and term[0] == '{' and term[-1] == '}':
                # we have found a command
                cmd = term[1:-1].lower()

                if cmd == 'tab':
                    val = '\t'
                elif cmd == 'return':
                    val = '\n'
                elif cmd.startswith('sleep '):
                    val = ''
                elif cmd.startswith('rate '):
                    val = ''
                elif cmd.startswith('remind '):
                    notify(cmd[7:])
                    val = ''
                else:
                    if cmd.startswith('paste '):
                        _, field = cmd.split(maxsplit=1)
                    else:
                        field = cmd
                        cmd = None
                    name, key = account.split_field(field)
                    try:
                        value = account.get_scalar(name, key)
                    except PasswordError as e:
                        if ask and e.is_collection and len(e.collection):
                            # is composite value, ask user which one is desired
                            choices = e.collection
                            if len(choices) == 1:
                                choice = choices.keys()[0]
                            else:
                                choice = show_list_dialog(
                                    'Choose from %s' % name,
                                    sorted(choices.keys())
                                )
                                if choice is None:
                                    raise PasswordError(
                                        'user abort.',
                                        culprit = (
                                            account.get_name(),
                                            account.combine_field(name, key)
                                        )
                                    )
                            key = choices[choice]
                            value = account.get_scalar(name, key)
                        else:
                            raise
                    try:
                        val = dedent(str(value)).strip()
                    except RecursionError:
                        raise PasswordError(
                            'script must not reference itself.', culprit=field
                        )

                    if not cmd:
                        if account.is_secret(name, key):
                            cmd = 'secret'
                        else:
                            cmd = 'value'
            else:
                cmd = 'text'
                val = term
                if not term:
                    continue
            yield cmd, val

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.script) class GeneratedSecret(object):
    """Base class for generated secrets"""

    def __new__(cls, *args, **kwargs):
        self = super(GeneratedSecret, cls).__new__(cls)
        self.reset()
        self.is_secret = True
        return self

    def __init__(self):
        """Constructor

        This base class should not be instantiated. A constructor is only
        provided to so the doctests work on the helper methods.
        """
        self.master = self.version = None

    def get_key_seed(self, default=None):
        """Get key seed.

        The default behavior is to pass the key in as the argument, and then
        simply use it as the return value so it will be used as a seed.  However,
        the subclasses can override this method and provide an alternative seed.
        This is used by Question. It returns the question so that it used rather
        than the index.
        """
        return default

    def get_description(self):
        """Get description.

        Returns description of the secret.
        This is used by Question. It returns the question as the description.
        """
        return None

    def initialize(self, account, field_name, field_key=None):
        if self.secret:
            return
        account_name = account.get_name()
        account_seed = account.get_seed()
        if self.master is None:
            master_seed = account.get_scalar('master_seed', default=None)
            master_source = account.get_scalar('_master_source_', default=None)
        else:
            master_seed = self.master
            master_source = 'secret'
        if not master_seed:
            master_seed = get_setting('user_key')
            master_source = 'user_key'
        if not master_seed:
            try:
                try:
                    master_seed = getpass.getpass(
                        'master seed for %s: ' % account_name
                    )
                    master_source = 'user'
                except EOFError:
                    output()
                if not master_seed:
                    warn("master seed is empty.")
            except (EOFError, KeyboardInterrupt):
                terminate()
        if self.version:
            version = self.version
        else:
            version = account.get_scalar('version', default='')

        log(
            'Generating secret ',
            '.'.join([str(n) for n in cull([account_name, field_name, field_key, version], remove=(None, ''))]),
            ', source of master seed: ',
            master_source,
            sep=''
        )
        field_key = self.get_key_seed(field_key)

        request_seed = account.request_seed()
        interactive_seed = ''
        if request_seed is True:
            try:
                interactive_seed = getpass.getpass(
                    'seed for %s: ' % account_name
                )
            except (EOFError, KeyboardInterrupt):
                terminate()
        elif callable(request_seed):
            interactive_seed = request_seed()
        elif is_str(request_seed):
            interactive_seed = request_seed
        elif request_seed:
            warn("invalid seed.")
        if request_seed and not interactive_seed:
            warn("seed is empty.")

        seeds = [
            master_seed,
            account_seed,
            field_name,
            field_key,
            version,
            interactive_seed
        ]
        self.set_seeds(seeds)
        assert(self.pool)

    def set_seeds(self, seeds):
        # Convert the seeds into 512 bit number
        key = ' '.join([str(seed) for seed in seeds])
        digest = hashlib.sha512((key).encode('utf-8')).digest()
        try:
            # convert from string to list of integers if this is python2
            digest = [ord(c) for c in digest]
        except TypeError:
            pass
        bits_per_byte = 8
        radix = 1 << bits_per_byte
        bits = 0
        for byte in digest:
            bits = radix * bits + byte
        self.pool = bits
        self.entropy = 0

    def reset(self):
        """
        A secret once generated will remember its value. With stealth secrets
        this is undesired because it prevents a new secret from being generated
        when account name changes. Calling this function causes the secret to
        forget its previously saved value, which will require generate to be
        called again.
        """
        self.secret = None
        self.pool = None

    def _partition(self, radix, num_partitions):
        """
        An iterator that returns a sequence of numbers. The length of the
        sequence is *num_partitions* and each number falls in the range
        [0:radix). The sequence of numbers seems random, but it is determined by
        the components that are passed into the constructor.

        >>> secret = GeneratedSecret()
        >>> secret.initialize(account, 'dux')
        >>> ' '.join([str(i) for i in secret._partition(100, 10)])
        '89 80 17 20 34 40 79 1 93 42'

        """
        assert self.pool, 'initialize() must be called first'
        max_index = radix - 1
        bits_per_chunk = (max_index).bit_length()
        self.entropy += num_partitions * math.log(radix, 2)

        for i in range(num_partitions):
            if self.pool < max_index:
                raise SecretExhausted()
            yield self.pool % radix
            self.pool = self.pool >> bits_per_chunk

    def _symbols(self, alphabet, num_symbols):
        """
        An iterator that returns a sequence of symbols. The length of the
        sequence is *num_symbols* and each symbol is chosen uniformly from the
        alphabet.

        >>> secret = GeneratedSecret()
        >>> secret.initialize(account, 'dux')
        >>> ' '.join(secret._symbols([str(i) for i in range(100)], 10))
        '89 80 17 20 34 40 79 1 93 42'

        This function can be used to generate a password as follows:
        >>> import string
        >>> alphabet = alphabet = string.ascii_letters + string.digits
        >>> ''.join(secret._symbols(alphabet, 16))
        'O7Dm0vMjJSMX2w30'

        This function can be used to generate a passphrase as follows:
        >>> dictionary = ['eeny', 'meeny', 'miny', 'moe']
        >>> ' '.join(secret._symbols(dictionary, 4))
        'eeny eeny moe miny'

        """
        assert self.pool, 'initialize() must be called first'
        if callable(alphabet):
            # Dictionary is passed as a function. That allows us to defer
            # reading the dictionary until we know it is really going to be
            # used as it is a slow operation.
            alphabet = alphabet()
        radix = len(alphabet)
        max_index = radix - 1
        bits_per_chunk = (max_index).bit_length()
        self.entropy += num_symbols * math.log(len(alphabet), 2)

        for i in range(num_symbols):
            if self.pool < max_index:
                raise SecretExhausted()
            yield alphabet[self.pool % radix]
            self.pool = self.pool >> bits_per_chunk

    def _get_index(self, radix):
        """
        Returns an index that falls in the range [0:radix).
        Can be called repeatedly with different values for the radix until the
        secret is exhausted.

        >>> secret = GeneratedSecret()
        >>> secret.initialize(account, 'dux')
        >>> ' '.join([str(secret._get_index(100)) for i in range(10)])
        '89 80 17 20 34 40 79 1 93 42'

        """
        assert self.pool, 'initialize() must be called first'
        max_index = radix - 1
        self.entropy += math.log(radix, 2)

        if self.pool < max_index:
            raise SecretExhausted()

        index = self.pool % radix

        bits_per_chunk = (max_index).bit_length()
        self.pool = self.pool >> bits_per_chunk
        return index

    def _get_symbol(self, alphabet):
        """
        Returns a symbol pulled from the alphabet.
        Can be called repeatedly with different values for the radix until the
        secret is exhausted.

        >>> secret = GeneratedSecret()
        >>> secret.initialize(account, 'dux')
        >>> ' '.join([str(secret._get_symbol(range(100))) for i in range(10)])
        '89 80 17 20 34 40 79 1 93 42'

        This function can be used to generate a birth date using:
        >>> def birthdate(secret, year, min_age=18, max_age=80):
        ...     return "%02d/%02d/%4d" % (
        ...         secret._get_symbol(range(12)) + 1,
        ...         secret._get_symbol(range(28)) + 1,
        ...         secret._get_symbol(range(year-max_age, year-min_age))
        ...     )
        >>> birthdate(secret, 2014)
        '11/19/1980'

        """
        assert self.pool, 'initialize() must be called first'
        radix = len(alphabet)
        max_index = radix - 1
        self.entropy += math.log(len(alphabet), 2)

        if self.pool < max_index:
            raise SecretExhausted()

        index = self.pool % radix

        bits_per_chunk = (max_index).bit_length()
        self.pool = self.pool >> bits_per_chunk

        return alphabet[index]

    # __repr__() {{{2
    def __repr__(self):
        secret = ObscuredSecret.hide(str(self))
        if self.is_secret:
            return "Hidden('{}')".format(secret)
        else:
            return "Hidden('{}', is_secret=False)".format(secret)

    # __str__() {{{2
    def __str__(self):
        return self.render()
from .browsers import StandardBrowser
from .collection import Collection
from .config import get_setting
from .error import PasswordError
from .obscure import ObscuredSecret
from .preferences import HIDDEN_TOOL_FIELDS, FORBIDDEN_TOOL_FIELDS
from .recognize import Recognizer
from .script import Script
from .secrets import GeneratedSecret
from difflib import get_close_matches
from inform import (
    codicil, conjoin, cull, full_stop, is_collection, is_mapping, is_str,
    join, log, notify, output, warn, indent, render
)
from textwrap import dedent
    from urllib.parse import urlparse
    from urlparse import urlparse
import re
        import getpass
            canonical = canonicalize(name)
            Account._accounts[canonical] = cls
            if canonical in seen:
                if name == account_name:
                    warn('duplicate account name.', culprit=name)
                else:
                    warn('alias duplicates existing name.', culprit=name)
                codicil('Seen in %s in %s.' % seen[canonical])
                codicil('And in %s in %s.' % (account_name, path))
                break
            else:
                new[canonical] = (account_name, path)
        seen.update(new)
            # this two step approach to updating seen prevents us from
            # complaining about aliases that duplicate the account name,
            # or duplicate aliases, both of which are harmless

    # id_contains() {{{2
    @classmethod
    def id_contains(cls, target):
        target = target.lower()
        if target in cls.get_name().lower():
            return True
        try:
            for alias in cls.aliases:
                if target in alias.lower():
                    return True
        except AttributeError:
            pass
        return False

    # account_contains() {{{2
    @classmethod
    def account_contains(cls, target):
        if cls.id_contains(target):
            return True
        target = target.lower()
        for key, value in cls.items():
            try:
                if is_collection(value):
                    for k, v in Collection(value).items():
                        if target in v.lower():
                            return True
                elif target in value.lower():
                    return True
            except AttributeError:
                pass
        return False

    # recognize() {{{2
    @classmethod
    def recognize(cls, data, verbose):
        # try the specified recognizers
        discovery = getattr(cls, 'discovery', ())
        for recognizer in Collection(discovery):
            if isinstance(recognizer, Recognizer):
                script = recognizer.match(data, cls, verbose)
                name = getattr(recognizer, 'name', None)
                if script:
                    yield name, script
        if discovery:
            return

        # If no recognizers specified, just check the urls
        for url in Collection(cls.get_composite('urls', default=[])):
            url = str(url)  # this is needed because url may be Script
            url = url if '//' in url else ('//'+url)
            url_components = urlparse(url)
            if url_components.scheme:
                protocol = url_components.scheme
            else:
                protocol = get_setting('default_protocol')
            host = url_components.netloc
            if host == data.get('host'):
                if (protocol == data.get('protocol')):
                    if verbose:
                        log('    %s: matches.' % cls.get_name())
                    yield None, True
                    return
                else:
                    msg = 'URL matches, but uses wrong protocol.'
                    notify(msg)
                    raise PasswordError(msg, culprit=cls.get_name())

    # initialize() {{{2
    @classmethod
    def initialize(cls, interactive_seed=False, stealth_name=None):
        cls._interactive_seed_ = interactive_seed
        log('Initializing account:', cls.get_name())
        try:
            if cls.master_seed.is_secure():
                if not cls._file_info_.encrypted:
                    warn(
                        'high value master seed not contained in encrypted',
                        'account file.', culprit=cls.get_name()
                    )
        except AttributeError:
            pass
        for bad, good in get_setting('commonly_mistaken_attributes').items():
            if hasattr(cls, bad):
                warn(
                    '{} attribute found,'.format(bad),
                    'should probably be {}.'.format(good),
                    culprit=cls.get_name()
                )

        # do some more error checking
        if isinstance(getattr(cls, 'master_seed', None), GeneratedSecret):
            raise PasswordError(
                'master must not be a subclass of GeneratedSecret.',
                culprit=cls.get_name()
            )

    # fields() {{{2
    @classmethod
    def fields(cls, all=False, sort=False):
        if sort:
            fields = sorted(cls.__dict__)
        else:
            fields = cls.__dict__

        hidden_fields = get_setting('hidden_fields').split() + HIDDEN_TOOL_FIELDS