(*args)
Take an arg or the key portion of a kwarg and check that it is in the
    set of allowed GPG options and flags, and that it has the correct
    type. Then, attempt to escape any unsafe characters. If an option is not
    allowed, drop it with a logged warning. Returns a dictionary of all
    sanitised, allowed options.

    Each new option that we support that is not a boolean, but instead has
    some additional inputs following it, i.e. "--encrypt-file foo.txt", will
    need some basic safety checks added here.

    GnuPG has three-hundred and eighteen commandline flags. Also, not all
    implementations of OpenPGP parse PGP packets and headers in the same way,
    so there is added potential there for messing with calls to GPG.

    For information on the PGP message format specification, see
    :rfc:`1991`.

    If you're asking, "Is this *really* necessary?": No, not really -- we could
    just follow the security precautions recommended by `this xkcd`__.

     __ https://xkcd.com/1181/

    :param str args: (optional) The boolean arguments which will be passed to
                     the GnuPG process.
    :rtype: str
    :returns: ``sanitised``
Take an arg or the key portion of a kwarg and check that it is in the
    set of allowed GPG options and flags, and that it has the correct
    type. Then, attempt to escape any unsafe characters. If an option is not
    allowed, drop it with a logged warning. Returns a dictionary of all
    sanitised, allowed options.
Take an arg or the key portion of a kwarg and check that it is in the set of allowed GPG options and flags and that it has the correct type . Then attempt to escape any unsafe characters . If an option is not allowed drop it with a logged warning . Returns a dictionary of all sanitised allowed options .
def _sanitise(*args):
    """Take an arg or the key portion of a kwarg and check that it is in the
    set of allowed GPG options and flags, and that it has the correct
    type. Then, attempt to escape any unsafe characters. If an option is not
    allowed, drop it with a logged warning. Returns a dictionary of all
    sanitised, allowed options.

    Each new option that we support that is not a boolean, but instead has
    some additional inputs following it, i.e. "--encrypt-file foo.txt", will
    need some basic safety checks added here.

    GnuPG has three-hundred and eighteen commandline flags. Also, not all
    implementations of OpenPGP parse PGP packets and headers in the same way,
    so there is added potential there for messing with calls to GPG.

    For information on the PGP message format specification, see
    :rfc:`1991`.

    If you're asking, "Is this *really* necessary?": No, not really -- we could
    just follow the security precautions recommended by `this xkcd`__.

     __ https://xkcd.com/1181/

    :param str args: (optional) The boolean arguments which will be passed to
                     the GnuPG process.
    :rtype: str
    :returns: ``sanitised``
    """

    ## see TODO file, tag :cleanup:sanitise:

    def _check_option(arg, value):
        """Check that a single ``arg`` is an allowed option.

        If it is allowed, quote out any escape characters in ``value``, and
        add the pair to :ivar:`sanitised`. Otherwise, drop them.

        :param str arg: The arguments which will be passed to the GnuPG
                        process, and, optionally their corresponding values.
                        The values are any additional arguments following the
                        GnuPG option or flag. For example, if we wanted to
                        pass ``"--encrypt --recipient isis@leap.se"`` to
                        GnuPG, then ``"--encrypt"`` would be an arg without a
                        value, and ``"--recipient"`` would also be an arg,
                        with a value of ``"isis@leap.se"``.

        :ivar list checked: The sanitised, allowed options and values.
        :rtype: str
        :returns: A string of the items in ``checked``, delimited by spaces.
        """
        checked = str()
        none_options        = _get_options_group("none_options")
        hex_options         = _get_options_group("hex_options")
        hex_or_none_options = _get_options_group("hex_or_none_options")

        if not _util._py3k:
            if not isinstance(arg, list) and isinstance(arg, unicode):
                arg = str(arg)

        try:
            flag = _is_allowed(arg)
            assert flag is not None, "_check_option(): got None for flag"
        except (AssertionError, ProtectedOption) as error:
            log.warn("_check_option(): %s" % str(error))
        else:
            checked += (flag + ' ')

            if _is_string(value):
                values = value.split(' ')
                for v in values:
                    ## these can be handled separately, without _fix_unsafe(),
                    ## because they are only allowed if they pass the regex
                    if (flag in none_options) and (v is None):
                        continue

                    if flag in hex_options:
                        if _is_hex(v): checked += (v + " ")
                        else:
                            log.debug("'%s %s' not hex." % (flag, v))
                            if (flag in hex_or_none_options) and (v is None):
                                log.debug("Allowing '%s' for all keys" % flag)
                        continue

                    elif flag in ['--keyserver']:
                        host = _check_keyserver(v)
                        if host:
                            log.debug("Setting keyserver: %s" % host)
                            checked += (v + " ")
                        else: log.debug("Dropping keyserver: %s" % v)
                        continue

                    ## the rest are strings, filenames, etc, and should be
                    ## shell escaped:
                    val = _fix_unsafe(v)
                    try:
                        assert not val is None
                        assert not val.isspace()
                        assert not v is None
                        assert not v.isspace()
                    except:
                        log.debug("Dropping %s %s" % (flag, v))
                        continue

                    if flag in ['--encrypt', '--encrypt-files', '--decrypt',
                                '--decrypt-files', '--import', '--verify']:
                        if ( (_util._is_file(val))
                             or
                             ((flag == '--verify') and (val == '-')) ):
                            checked += (val + " ")
                        else:
                            log.debug("%s not file: %s" % (flag, val))

                    elif flag in ['--cipher-algo', '--personal-cipher-prefs',
                                  '--personal-cipher-preferences']:
                        legit_algos = _check_preferences(val, 'cipher')
                        if legit_algos: checked += (legit_algos + " ")
                        else: log.debug("'%s' is not cipher" % val)

                    elif flag in ['--compress-algo', '--compression-algo',
                                  '--personal-compress-prefs',
                                  '--personal-compress-preferences']:
                        legit_algos = _check_preferences(val, 'compress')
                        if legit_algos: checked += (legit_algos + " ")
                        else: log.debug("'%s' not compress algo" % val)

                    elif flag == '--trust-model':
                        legit_models = _check_preferences(val, 'trust')
                        if legit_models: checked += (legit_models + " ")
                        else: log.debug("%r is not a trust model", val)

                    elif flag == '--pinentry-mode':
                        legit_modes = _check_preferences(val, 'pinentry')
                        if legit_modes: checked += (legit_modes + " ")
                        else: log.debug("%r is not a pinentry mode", val)

                    else:
                        checked += (val + " ")
                        log.debug("_check_option(): No checks for %s" % val)

        return checked.rstrip(' ')

    is_flag = lambda x: x.startswith('--')

    def _make_filo(args_string):
        filo = arg.split(' ')
        filo.reverse()
        log.debug("_make_filo(): Converted to reverse list: %s" % filo)
        return filo

    def _make_groups(filo):
        groups = {}
        while len(filo) >= 1:
            last = filo.pop()
            if is_flag(last):
                log.debug("Got arg: %s" % last)
                if last == '--verify':
                    groups[last] = str(filo.pop())
                    ## accept the read-from-stdin arg:
                    if len(filo) >= 1 and filo[len(filo)-1] == '-':
                        groups[last] += str(' - ') ## gross hack
                        filo.pop()
                else:
                    groups[last] = str()
                while len(filo) > 1 and not is_flag(filo[len(filo)-1]):
                    log.debug("Got value: %s" % filo[len(filo)-1])
                    groups[last] += (filo.pop() + " ")
                else:
                    if len(filo) == 1 and not is_flag(filo[0]):
                        log.debug("Got value: %s" % filo[0])
                        groups[last] += filo.pop()
            else:
                log.warn("_make_groups(): Got solitary value: %s" % last)
                groups["xxx"] = last
        return groups

    def _check_groups(groups):
        log.debug("Got groups: %s" % groups)
        checked_groups = []
        for a,v in groups.items():
            v = None if len(v) == 0 else v
            safe = _check_option(a, v)
            if safe is not None and not safe.strip() == "":
                log.debug("Appending option: %s" % safe)
                checked_groups.append(safe)
            else:
                log.warn("Dropped option: '%s %s'" % (a,v))
        return checked_groups

    if args is not None:
        option_groups = {}
        for arg in args:
            ## if we're given a string with a bunch of options in it split
            ## them up and deal with them separately
            if (not _util._py3k and isinstance(arg, basestring)) \
                    or (_util._py3k and isinstance(arg, str)):
                log.debug("Got arg string: %s" % arg)
                if arg.find(' ') > 0:
                    filo = _make_filo(arg)
                    option_groups.update(_make_groups(filo))
                else:
                    option_groups.update({ arg: "" })
            elif isinstance(arg, list):
                log.debug("Got arg list: %s" % arg)
                arg.reverse()
                option_groups.update(_make_groups(arg))
            else:
                log.warn("Got non-str/list arg: '%s', type '%s'"
                         % (arg, type(arg)))
        checked = _check_groups(option_groups)
        sanitised = ' '.join(x for x in checked)
        return sanitised
    else:
        log.debug("Got None for args")
def _sanitise ( * args ) : ## see TODO file, tag :cleanup:sanitise: def _check_option ( arg , value ) : """Check that a single ``arg`` is an allowed option.

        If it is allowed, quote out any escape characters in ``value``, and
        add the pair to :ivar:`sanitised`. Otherwise, drop them.

        :param str arg: The arguments which will be passed to the GnuPG
                        process, and, optionally their corresponding values.
                        The values are any additional arguments following the
                        GnuPG option or flag. For example, if we wanted to
                        pass ``"--encrypt --recipient isis@leap.se"`` to
                        GnuPG, then ``"--encrypt"`` would be an arg without a
                        value, and ``"--recipient"`` would also be an arg,
                        with a value of ``"isis@leap.se"``.

        :ivar list checked: The sanitised, allowed options and values.
        :rtype: str
        :returns: A string of the items in ``checked``, delimited by spaces.
        """ checked = str ( ) none_options = _get_options_group ( "none_options" ) hex_options = _get_options_group ( "hex_options" ) hex_or_none_options = _get_options_group ( "hex_or_none_options" ) if not _util . _py3k : if not isinstance ( arg , list ) and isinstance ( arg , unicode ) : arg = str ( arg ) try : flag = _is_allowed ( arg ) assert flag is not None , "_check_option(): got None for flag" except ( AssertionError , ProtectedOption ) as error : log . warn ( "_check_option(): %s" % str ( error ) ) else : checked += ( flag + ' ' ) if _is_string ( value ) : values = value . split ( ' ' ) for v in values : ## these can be handled separately, without _fix_unsafe(), ## because they are only allowed if they pass the regex if ( flag in none_options ) and ( v is None ) : continue if flag in hex_options : if _is_hex ( v ) : checked += ( v + " " ) else : log . debug ( "'%s %s' not hex." % ( flag , v ) ) if ( flag in hex_or_none_options ) and ( v is None ) : log . debug ( "Allowing '%s' for all keys" % flag ) continue elif flag in [ '--keyserver' ] : host = _check_keyserver ( v ) if host : log . debug ( "Setting keyserver: %s" % host ) checked += ( v + " " ) else : log . debug ( "Dropping keyserver: %s" % v ) continue ## the rest are strings, filenames, etc, and should be ## shell escaped: val = _fix_unsafe ( v ) try : assert not val is None assert not val . isspace ( ) assert not v is None assert not v . isspace ( ) except : log . debug ( "Dropping %s %s" % ( flag , v ) ) continue if flag in [ '--encrypt' , '--encrypt-files' , '--decrypt' , '--decrypt-files' , '--import' , '--verify' ] : if ( ( _util . _is_file ( val ) ) or ( ( flag == '--verify' ) and ( val == '-' ) ) ) : checked += ( val + " " ) else : log . debug ( "%s not file: %s" % ( flag , val ) ) elif flag in [ '--cipher-algo' , '--personal-cipher-prefs' , '--personal-cipher-preferences' ] : legit_algos = _check_preferences ( val , 'cipher' ) if legit_algos : checked += ( legit_algos + " " ) else : log . debug ( "'%s' is not cipher" % val ) elif flag in [ '--compress-algo' , '--compression-algo' , '--personal-compress-prefs' , '--personal-compress-preferences' ] : legit_algos = _check_preferences ( val , 'compress' ) if legit_algos : checked += ( legit_algos + " " ) else : log . debug ( "'%s' not compress algo" % val ) elif flag == '--trust-model' : legit_models = _check_preferences ( val , 'trust' ) if legit_models : checked += ( legit_models + " " ) else : log . debug ( "%r is not a trust model" , val ) elif flag == '--pinentry-mode' : legit_modes = _check_preferences ( val , 'pinentry' ) if legit_modes : checked += ( legit_modes + " " ) else : log . debug ( "%r is not a pinentry mode" , val ) else : checked += ( val + " " ) log . debug ( "_check_option(): No checks for %s" % val ) return checked . rstrip ( ' ' ) is_flag = lambda x : x . startswith ( '--' ) def _make_filo ( args_string ) : filo = arg . split ( ' ' ) filo . reverse ( ) log . debug ( "_make_filo(): Converted to reverse list: %s" % filo ) return filo def _make_groups ( filo ) : groups = { } while len ( filo ) >= 1 : last = filo . pop ( ) if is_flag ( last ) : log . debug ( "Got arg: %s" % last ) if last == '--verify' : groups [ last ] = str ( filo . pop ( ) ) ## accept the read-from-stdin arg: if len ( filo ) >= 1 and filo [ len ( filo ) - 1 ] == '-' : groups [ last ] += str ( ' - ' ) ## gross hack filo . pop ( ) else : groups [ last ] = str ( ) while len ( filo ) > 1 and not is_flag ( filo [ len ( filo ) - 1 ] ) : log . debug ( "Got value: %s" % filo [ len ( filo ) - 1 ] ) groups [ last ] += ( filo . pop ( ) + " " ) else : if len ( filo ) == 1 and not is_flag ( filo [ 0 ] ) : log . debug ( "Got value: %s" % filo [ 0 ] ) groups [ last ] += filo . pop ( ) else : log . warn ( "_make_groups(): Got solitary value: %s" % last ) groups [ "xxx" ] = last return groups def _check_groups ( groups ) : log . debug ( "Got groups: %s" % groups ) checked_groups = [ ] for a , v in groups . items ( ) : v = None if len ( v ) == 0 else v safe = _check_option ( a , v ) if safe is not None and not safe . strip ( ) == "" : log . debug ( "Appending option: %s" % safe ) checked_groups . append ( safe ) else : log . warn ( "Dropped option: '%s %s'" % ( a , v ) ) return checked_groups if args is not None : option_groups = { } for arg in args : ## if we're given a string with a bunch of options in it split ## them up and deal with them separately if ( not _util . _py3k and isinstance ( arg , basestring ) ) or ( _util . _py3k and isinstance ( arg , str ) ) : log . debug ( "Got arg string: %s" % arg ) if arg . find ( ' ' ) > 0 : filo = _make_filo ( arg ) option_groups . update ( _make_groups ( filo ) ) else : option_groups . update ( { arg : "" } ) elif isinstance ( arg , list ) : log . debug ( "Got arg list: %s" % arg ) arg . reverse ( ) option_groups . update ( _make_groups ( arg ) ) else : log . warn ( "Got non-str/list arg: '%s', type '%s'" % ( arg , type ( arg ) ) ) checked = _check_groups ( option_groups ) sanitised = ' ' . join ( x for x in checked ) return sanitised else : log . debug ( "Got None for args" )