#!/bin/bash
#---------------------------------------------------------------------
##
## @Libgrimoire
##
## @Synopsis Set of functions containing the spell writing API.
##
##
## These functions can be used in the PRE_BUILD, BUILD, POST_BUILD
## and POST_INSTALL sections of spells.
##
## @Copyright
## Original version Copyright 2001 by Kyle Sallee
## Additions/Corrections Copyright 2002 by the Source Mage Team
## New World libunpack Additions/Corrections by Seth Woolley (2005)
##
#---------------------------------------------------------------------

#===================== libunpack common ==============================

#---------------------------------------------------------------------
## @Type API
## @param SOURCE suffix
##
## unpack_file takes the SOURCE suffix and figures out if it is supposed
## to hash or gpg check it -- then it does its dirty work and runs unpack_hash
## or unpack_gpg depending upon the circumstances.  That's the only argument it
## takes and needs: '' '2' '3', etc.  It is run in default_pre_build for the
## null argument only.  Custom unpacking still requires a custom PRE_BUILD.
##
## valid formats: vendor-provided gpg, guru-provided gpg, any
## hash-algorithm provided by gpg (currently md5, sha1, sha256, sha384,
## sha512, ripemd160
##
##           SOURCE=blah
##          SOURCE2=blah.asc
##       SOURCE_URL=http://blah.com/$SOURCE
##      SOURCE2_URL=http://blah.com/$SOURCE2
##       SOURCE_GPG=blah.gpg:$SOURCE2:UPSTREAM_KEY
##   SOURCE2_IGNORE=signature # for auditing purposes
##
##           SOURCE=blah
##       SOURCE_URL=http://blah.com/$SOURCE
##       SOURCE_GPG=swoolley.gpg:$SOURCE.asc:WORKS_FOR_ME
##
##           SOURCE=blah
##       SOURCE_URL=http://blah.com/$SOURCE
##           MD5[0]=d41d8cd98f00b204e9800998ecf8427e
##
##           SOURCE=blah
##       SOURCE_URL=http://blah.com/$SOURCE
##       SOURCE_HASH=md5:d41d8cd98f00b204e9800998ecf8427e:WORKS_FOR_ME
##
## In GPG mode:
##   Validates the verification level (the third parameter) and the
##   hash algorithm against user defined lists.
##   It finds the public key and signature using locate_spell_file,
##   Then it validates it at the beginning.
##   see unpack_gpg()
##
## In HASH mode:
##   Validates the verification level (the third parameter) and the
##   hash algorithm against user defined lists.
##   It uses gpg to calculate the hash value except for md5 and sha1, which
##   coreutils provides.
##   see unpack_hash()
##
## In IGNORE mode:
##   It checks for the following text:
##     volatile (for cvs/svn/any-other-scm)
##     unversioned (the source file changes frequently, but not a direct scm)
##     signature (for gnupg signatures)
##   as reasons for ignoring the source code validation.  Signatures
##   are silently ignored.  Everything else respects MD5SUM_DL.
##   see unpack_ignore
##
## Otherwise, it falls back to MISSING mode, see unpack_missing
## (or for now)
## Otherwise, it falls back to old uncompressed md5sum check with MD5[n].
##   see real_unpack()
##
## The default verification level is "WORKS_FOR_ME"
##
## Verification levels are, these indicate how much effort was put into
## validating the integrity of the source from the upstream vendor.
##   WORKS_FOR_ME No verification was done.
##   UPSTREAM_HASH Checked the upstream hash file
##   UPSTREAM_KEY Checked upstream (gpg) key, signature matched, but the
##                key was not validated
##   ESTABLISHED_UPSTREAM_KEY Upstream key was not validated against
##                            multiple independent sources, but has been
##                            in use for several years
##   VERIFIED_UPSTREAM_KEY Upstream key id was verified against multiple
##                         independent sources.
##   ID_CHECK_UPSTREAM_KEY Key was verified in person with a photo id check.
##
## Also if you want to include more than one signature, hash, etc, just put
## a 2, 3, 4, etc on the end of the variable like so:
##   SOURCE2_HASH2=...
##
## For cascading, currently it will still ask abort questions: a no abort
## will make it fail over all cascades; a yes abort will have it skip to
## the next cascdes.  Missing binaries or other failures like that (error 200
## below) will silently fail over to the next check.  The cascade order is:
##  GPG, HASH, IGNORE, MISSING
##
## The cascade setup allows you to place a higher bit checksum earlier 
## in the cascade and even if the binary doesn't work it will just print 
## out an abort query which can be said no to and it will continue to 
## fail over to the lower bit checksum that should be available in 
## coreutils (like sha1/md5).  That's if you're not using gpg, which is 
## preferred.  If multiple hashes are included of different ciphers, the
## user can abort on either that go bad, so it can be considered a
## security increase to have more than one, but only if the harder cipher
## is first in the cascade order, as the first successful hash match will
## go ahead and prompt an untarball.  I may change it later, but for now I
## think first successful match skipping the rest is least intrusive, and
## I'd need to add an interface element to let the user choose to run all
## checks on a single source.
##
#---------------------------------------------------------------------
function real_unpack_file() {
  debug "libgrimoire" "real_unpack_file - $*"

  local GPGNUM="$1"
  local SVAR="SOURCE${GPGNUM}"

  local crypto_func
  for crypto_func in GPG HASH IGNORE; do
    debug "libgrimoire" "checking $crypto_func verification"
    local AVAR="SOURCE${GPGNUM}_${crypto_func}"
    local AVARN="$AVAR"
    local iter=0
    local rc=""
    # NOTE ## date: 2005-08-25
    # This while loop runs through the SOURCEn_{GPG|HASH|IGNORE}m not the
    # SOURCEn_*[m] set of vars this is kinda confusing we might rip it out
    while [ -n "${!AVARN}" ]; do
      local lcase_crypto_func="$(echo $crypto_func | tr 'A-Z' 'a-z')"
      unpack_$lcase_crypto_func "${!SVAR}" "${!AVARN}"
      rc="$?"
      case "$rc" in
        200) debug "libgrimoire" "falling back from $AVARN"; rc="" ;;
          1)                               return "$rc"            ;;
          0) uncompress_unpack "${!SVAR}"; return "$?"             ;;
      esac
      (( iter++ ))
      AVARN="$AVAR[$iter]"
    done
    [ -n "$rc" ] && return "$rc"
  done

  if false; then # <------ here's the switch to disable oldworld -------
    debug "libgrimoire" "falling back to missing verification"
    unpack_missing "${!SVAR}"
    rc="$?"
    case "$rc" in
        0) uncompress_unpack "${!SVAR}"; return "$?"             ;;
        *) return "$rc"                                          ;;
    esac
  else
    debug "libgrimoire" "falling back to regular MD5[]"
    local MD5NUM="$([ -z "$GPGNUM" ] && echo 0 || echo "$(($GPGNUM - 1))")"
    real_unpack "${!SVAR}" "${MD5[$MD5NUM]}"
  fi
}


#---------------------------------------------------------------------
## @param filename 
## @param compressor 
## @Stdout uncompressed
##
## Just uncompresses the file, but does not expand it. i.e. bunzip
## it, but don't untar it. It dumps the expanded file to stdout.
## Note: zip is a special case because it doesn't work with streams.
##
#---------------------------------------------------------------------
function uncompress_core() {
  debug "libgrimoire" "uncompress_core - $*"

  case  "$2"  in
          bzip2)  bzip2  -cdf   "$1"  ;;
           gzip)  gzip   -cdf   "$1"  ;;
      compress*)  gzip   -cdf   "$1"  ;;
            Zip)  cat           "$1"  ;;
            RPM)  rpmunpack  <  "$1" | gzip  -cd    ;;
            tar)  cat           "$1"  ;;
              *)  cat           "$1"  ;;
  esac

}


#---------------------------------------------------------------------
## @param filename 
## @param compressor 
## @Stdout uncompressed
##
## unpack_core takes the uncompressed stream and turns it into the
## fully unarchived form.
## Note: zip is a special case because it doesn't work with streams.
##
#---------------------------------------------------------------------
function unpack_core() {
  debug "libgrimoire" "unpack_core - $*"

  case  "$2"  in
            bzip2|gzip|compress*|tar)
                    tar   --owner=root  --group=root  -xf  /dev/stdin \
                          2> /dev/null  ||  cat > /dev/null            ;;
              Zip)  cat /dev/stdin >/dev/null   #get rid of unused output
                    unzip  -q  "$1"                                    ;;
              RPM)  cpio  -idm < /dev/stdin                            ;;
                *)  cat > /dev/null                                    ;;
  esac

}


#---------------------------------------------------------------------
## @Type API
## @param filename
## @Stdout compressor
##
## Guesses what program was used to compress a file
## Return value is always success due to `file' workings
##
#---------------------------------------------------------------------
function real_guess_compressor()  {
  # NOTE: if the file doesn't exist, `file' still completes successfully
  #       the COMPRESSOR value in this case will be "can't"

  local OUTPUT="$($FILEPROG -b "$1")"
  local COMPRESSOR="$(echo "$OUTPUT" | cut -d ' ' -f1)"
  [ "$COMPRESSOR" = "GNU" -o "$COMPRESSOR" = "POSIX" ] &&
    COMPRESSOR="$(echo "$OUTPUT" | cut -d ' ' -f2)"
  debug "libgrimoire" "guess_compressor() - guessed $1 compressor <$COMPRESSOR>"
  echo "$COMPRESSOR"
}


#---------------------------------------------------------------------
## @Type API
##
## alias function (was uncompress_md5)
##
## Used to be uncompress_md5(), now it is uncompress_core()
##
#---------------------------------------------------------------------
function real_uncompress() { uncompress_core "$@"; }


#---------------------------------------------------------------------
## @param required spell
##
## Returns 200 if the user says not to Abort in the face, otherwise
##
#---------------------------------------------------------------------
function unpack_spell_required() {
  debug "libgrimoire" "Running unpack_spell_required -- $1"

  if ! spell_ok "$1" ; then
    query "This spell has an option to check its integrity via spell "\
"${SPELL_COLOR}${1}${QUERY_COLOR} for $2, you might consider casting it. "\
"Abort?" n && 
      return 1 ||
        return 200
  else
    return 0
  fi

}


#===================== libunpack newworld ============================

#--------------------------------------------------------------------
## @param the verification level
##
## returns 0 if the specified verification level is in the user's
## list of allowed verification levels, or if they allow unknown
## verification levels, 1 otherwise
##
#--------------------------------------------------------------------
function is_allowed_verf_level() {
  local rc=0
  local VRFLEVEL=$1
  message "${MESSAGE_COLOR}Checking spell level ${VRFLEVEL}${DEFAULT_COLOR}"
  if list_find "${VRF_ALLOWED_LEVELS}" "${VRFLEVEL}:on"
  then
    message "${MESSAGE_COLOR}Spell level is an allowed level${DEFAULT_COLOR}"
  elif list_find "${VRF_ALLOWED_LEVELS}" "${VRFLEVEL}:off"
  then
      message "${PROBLEM_COLOR}Spell level is not an allowed level${DEFAULT_COLOR}"
      rc=1
  else
    if [[ "${VRF_ALLOW_NEW_LEVELS}" == "on" ]]
    then
      message "${MESSAGE_COLOR}Spell level is a new allowed level${DEFAULT_COLOR}"
    else
      message "${PROBLEM_COLOR}Spell level is not an allowed level${DEFAULT_COLOR}"
      rc=1
    fi
  fi
  return $rc
}

#--------------------------------------------------------------------
## @param hash used
## @param spells verification level
##
## first checks if the hash is in the user specified list in an on state then
## checks if the hash is there in an off state, if it can't find either then
## it checks the state of VRF_ALLOW_NEW_HASHES to see if we should succeed or
## not
## Returns 0 if the hash is allowed or (VRF_ALLOW_NEW_HASHES is on and the hash
## is not present in the hash list)
##
#--------------------------------------------------------------------
function is_allowed_hash() {
  local rc=0
  local hash=$1
  local HASHLEVEL=$2
  message "${MESSAGE_COLOR}Algorithm used: ${hash}${DEFAULT_COLOR}" &&
  if list_find "$VRF_ALLOWED_HASHES" "${hash}:on"
  then
    message "${MESSAGE_COLOR}Algorithm checks out${DEFAULT_COLOR}"
    if is_allowed_verf_level $HASHLEVEL ; then rc=0 ; else rc=1 ; fi
  elif list_find "$VRF_ALLOWED_HASHES" "${hash}:off"
  then
    message "${PROBLEM_COLOR}Algorithm is not in user selected list${DEFAULT_COLOR}"
    rc=1
  elif [[ "$VRF_ALLOW_NEW_HASHES" == "on" ]]
  then
    message "${MESSAGE_COLOR}Allowing new hash${hash}${DEFAULT_COLOR}"
    if is_allowed_verf_level $HASHLEVEL ; then rc=0 ; else rc=1; fi
  else
    message "${PROBLEM_COLOR}Disallowing new hash ${hash}${DEFAULT_COLOR}"
    rc=1
  fi
  return $rc
}

#---------------------------------------------------------------------
## @param file to unpack 
## @param gpg public key file (.gpg) ":" gpg signature file  (.asc)
##
## Given a file, unpack checks the gpg signature for that file, and, if 
## appropriate, runs the decompression program for that file, as well as 
## untar'ing the file. Note: zip is a special case because it doesn't 
## work with streams.
##
#---------------------------------------------------------------------
function unpack_gpg() {
  debug "libgrimoire" "Running unpack_gpg -- $*"

  local FILENAME="$( guess_filename   "$SOURCE_CACHE/$1" )"
  local PFNAME="$( echo "$2" | cut -d: -f1  )"
  local SFNAME="$( echo "$2" | cut -d: -f2  )"
  local GPGLEVEL="$( echo "$2" | cut -d: -f3 )"
  if [[ -z $GPGLEVEL ]]
  then
    GPGLEVEL=$DEFAULT_SPELL_VRF_LEVEL
  elif ! list_find "${VERIFY_SPELL_LEVELS}" "${GPGLEVEL}"
  then
    message "${PROBLEM_COLOR}This is probably a spell bug ${GPGLEVEL} is not in ${VERIFY_SPELL_LEVELS}${DEFAULT_COLOR}"
    return 1
  fi
  local GPGALGO_USED=""
  local message_file=""

  message "${MESSAGE_COLOR}GPG checking source file $1...${DEFAULT_COLOR}"

  unpack_spell_required gnupg || return "$?"

  gpg_verify_signature "$( locate_spell_file "$SFNAME" )" \
                       "$FILENAME" \
                       "$( locate_spell_file "$PFNAME" securely)" GPGALGO_USED
  rc="$?"
  case "$rc" in
    0)
      if is_allowed_hash $GPGALGO_USED $GPGLEVEL ; then rc=0 ; else rc=1 ; fi
      ;;
    3) message_file="Signature" ;;
    4) message_file="Source" ;;
    5) message_file="Keyring" ;;
  esac
  if [[ $message_file ]]
  then
      message "${PROBLEM_COLOR}AHHH!!! ${message_file} file not found${DEFAULT_COLOR}"
  fi
  if [ "$rc" -eq 200 ]; then 
    return 200
  fi
  
  gpg_user_query $rc $SPELL spell || return 1
  return 0

}


#---------------------------------------------------------------------
## @param file to unpack 
## @param algorithm ":" hashsum
##
## Given a file, unpack checks the hash for that file, and, if 
## appropriate, runs the decompression program for that file, as well as 
## untar'ing the file. Note: zip is a special case because it doesn't 
## work with streams.
##
#---------------------------------------------------------------------
function unpack_hash() {
  debug "libgrimoire" "Running unpack_hash() on $1"

  local FILENAME="$( guess_filename   "$SOURCE_CACHE/$1" )"
  local ALGORITHM="$( echo "$2" | cut -d: -f1  )"
  local HASHSUM="$(   echo "$2" | cut -d: -f2  )"
  local HLEVEL="$(    echo "$2" | cut -d: -f3  )"
  local rc=0
  if [[ -z "$HLEVEL" ]]
  then
    HLEVEL=$DEFAULT_SPELL_VRF_LEVEL
  fi

  message "${MESSAGE_COLOR}hash checking source file $1...${DEFAULT_COLOR}"
  local HASH
  if [ "$MD5SUM_DL" != "off" ]; then

    if [[ "$ALGORITHM" == md5 ]] || [[ "$ALGORITHM" == sha1 ]] ; then
      unpack_spell_required coreutils "$ALGORITHM" || return "$?" 
      HASH="$(${ALGORITHM}sum "$FILENAME" | cut -d' ' -f1)"
    else
      unpack_spell_required gnupg "$ALGORITHM" || return "$?"
      if list_find "$(gpg_get_hashes)" $ALGORITHM; then
        HASH="$(gpg_hashsum "${ALGORITHM}" "$FILENAME" | cut -d' ' -f1)"
      else 
        message "${PROBLEM_COLOR}Algorithm $ALGORITHM is not"\
                "known!${DEFAULT_COLOR}"
        return 200
      fi
    fi
    local rc=$?

    if [[ "$HASH" != "$HASHSUM" ]] || [[ $rc != 0 ]]
    then
      message "${PROBLEM_COLOR}$ALGORITHM check failed"  \
              "$HASH != $HASHSUM !${DEFAULT_COLOR}"      &&
      hash_user_query 1 "$SPELL" spell || return 1
    else
      if is_allowed_hash ${ALGORITHM} ${HLEVEL} ; then rc=0 ; else rc=1 ; fi
      hash_user_query $rc "$SPELL" spell || return 1
    fi
  else
    message "${PROBLEM_COLOR}Continuing!${DEFAULT_COLOR}"
  fi
  return 0
}

#--------------------------------------------------------------------
## @param return code from unpack_hash
## @param spell name 
## 
## Does some basic output to tell the user what failed and how then calls
## unpack_file_user_query
## Returns 0 if hash succeeded otherwise returns 1 if unpack_file_user_query 
## fails
##
#--------------------------------------------------------------------
function hash_user_query() {
  local rc=$1
  local spell=$2
  case "$rc" in
    0) 
      message "${MESSAGE_COLOR}hash verification succeeded${DEFAULT_COLOR}"
      ;;
    *)
      message "${PROBLEM_COLOR}hash verification failure${DEFAULT_COLOR}"
      unpack_file_user_query $rc || return 1
      ;;
  esac
  return 0
}

#--------------------------------------------------------------------
## @param return code from the unpack_gpg or unpack_hash
##
## checks MD5SUM_DL to abort or not 
## Returns what query returns if it's called
##
#--------------------------------------------------------------------
function unpack_file_user_query() {
  local rc=$1
  case "$rc" in
    0)
      ;;
    *)
      case "$MD5SUM_DL" in
        ask_ignore)  query "Abort?" "n" && return 1  ;;
        ask_risky|ask_abort)  query "Abort?" "y" && return 1 ;;
        on|abort_all|*) message "${RED}Aborting.${DEFAULT_COLOR}" ; return 1 ;;
      esac
      ;;
  esac
  return 0
}

#---------------------------------------------------------------------
## @param file to unpack 
## @param reason to ignore it, one of: volatile unversioned signature
##
## Given a file, unpack checks the ignore rules for that file, and, if 
## appropriate, runs the decompression program for that file, as well as 
## untar'ing the file. Note: zip is a special case because it doesn't 
## work with streams.
##
#---------------------------------------------------------------------
function unpack_ignore() {
  debug "libgrimoire" "Running unpack_ignore() on $1"

  REASON="$2"

  message "${MESSAGE_COLOR}Not checking ${2} source file $1...${DEFAULT_COLOR}"

  if [ "$MD5SUM_DL" != "off" ]; then

    [ "$REASON" == "signature" ]  ||
      case "$MD5SUM_DL" in
ask_risky|ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
           abort_all)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
      ask_abort|on|*)  query "Abort?" "y"         && return 1   ||  return 0 ;;
      esac

  else
    message "${RED}Continuing!${DEFAULT_COLOR}"
    return 0
  fi

}


#---------------------------------------------------------------------
## @param file to unpack 
## @param reason to ignore it, one of: volatile unversioned signature
##
## Given a file, unpack checks the ignore rules for that file, and, if 
## appropriate, runs the decompression program for that file, as well as 
## untar'ing the file. Note: zip is a special case because it doesn't 
## work with streams.
##
#---------------------------------------------------------------------
function unpack_missing() {
  debug "libgrimoire" "Running unpack_missing() on $1"

  message "${PROBLEM_COLOR}Missing check for source file $1!${DEFAULT_COLOR}"

  if [ "$MD5SUM_DL" != "off" ]; then

    case "$MD5SUM_DL" in
          ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
 ask_risky|ask_abort)  query "Abort?" "y"         && return 1   ||  return 0 ;;
      on|abort_all|*)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
    esac

  else
    message "${RED}Continuing!${DEFAULT_COLOR}"
    return 0
  fi

}


#---------------------------------------------------------------------
## @param file to unpack 
##
## Given a file, runs the decompression program for that file, as well as 
## untar'ing the file. 
##
#---------------------------------------------------------------------
function uncompress_unpack() {
  debug "libgrimoire" "Running uncompress_unpack() on $1"

  FILENAME="$(   guess_filename   "$SOURCE_CACHE/$1" )" &&
  COMPRESSOR="$( guess_compressor "$FILENAME"        )"

  message "${MESSAGE_COLOR}Unpacking source file ${SPELL_COLOR}${1}"   \
          "${DEFAULT_COLOR}${MESSAGE_COLOR}for spell${SPELL_COLOR}"    \
          "${SPELL}${DEFAULT_COLOR}${MESSAGE_COLOR}.${DEFAULT_COLOR}"

  if [[ ! $FILENAME ]] || ! test -f "$FILENAME" ; then
    message "${PROBLEM_COLOR}Source file not found.${DEFAULT_COLOR}"
    return 1
  fi

  uncompress_core "$FILENAME" "$COMPRESSOR" |
      unpack_core "$FILENAME" "$COMPRESSOR"
}

#---------------------------------------------------------------------
## @param file to unpack 
##
## Interface to unpack a file without any verification.
##
#---------------------------------------------------------------------
function real_unpack_file_simple() { uncompress_unpack "$@"; }


#---------------------------------------------------------------------
## @param absolute or relative file path
## @param empty or 'securely', which would skip SOURCE_CACHE
## @Stdout the real path of the file (sometimes relative to CWD)
##
## Given a file, locate_spell_file finds out where it really is within
## the spell hierarchy down to the grimoire root, and then tries cwd and
## then the source cache.
##
#---------------------------------------------------------------------
function locate_spell_file() {
  debug "libgrimoire" "Running locate_spell_file() $2 on $1"

  # checks in any case
  [ -f    "$SPELL_DIRECTORY/$1" ] && echo    "$SPELL_DIRECTORY/$1" && return 0
  [ -f  "$SECTION_DIRECTORY/$1" ] && echo  "$SECTION_DIRECTORY/$1" && return 0
  [ -f           "$GRIMOIRE/$1" ] && echo           "$GRIMOIRE/$1" && return 0
  [ -f                     "$1" ] && echo                     "$1" && return 0

  [ "$2" != "securely" ] &&  # checks in "secure" mode
  [ -f       "$SOURCE_CACHE/$1" ] && echo       "$SOURCE_CACHE/$1" && return 0

  message "${MESSAGE_COLOR}"                                 \
          "Problem: $1: file not found in spell hierarchy."  \
          "${DEFAULT_COLOR}"                                 > /dev/stderr
  echo  "$1"
  return 1

}


#===================== libunpack oldworld ============================

#---------------------------------------------------------------------
## @Type API
## @param file to unpack 
## @param md5sum
##
## Given a file, unpack runs the decompression program for that file,
## as well as untar'ing the file if appropriate and if the MD5
## matches.
## Note: zip is a special case because it doesn't work with streams.
##
#---------------------------------------------------------------------
function real_unpack() {
  debug "libgrimoire" "Running unpack -- $*"

  message "${MESSAGE_COLOR}Unpacking source file ${SPELL_COLOR}${1}"   \
          "${DEFAULT_COLOR}${MESSAGE_COLOR}for spell${SPELL_COLOR}"    \
          "${SPELL}${DEFAULT_COLOR}${MESSAGE_COLOR}.${DEFAULT_COLOR}"

  FILENAME="$(guess_filename  "$SOURCE_CACHE/$1")" &&
  COMPRESSOR="$(guess_compressor "$FILENAME")"

  if [[ ! $FILENAME ]] || ! test -f "$FILENAME" ; then
    message "${PROBLEM_COLOR}Source file not found.${DEFAULT_COLOR}"
    return 1
  fi

  uncompress_md5 "$FILENAME" "$COMPRESSOR" "$2" |
     unpack_core "$FILENAME" "$COMPRESSOR"      &&
  {

    # This section takes care of what happens if the md5sum doesn't match.
    # $TMP_DIR/libgrimoire.uncompress.$$ is set in uncompress. It's the only
    # way to get the return value since it's in a pipe.
    if ! [[ $2 ]] ; then

      rm "$TMP_DIR/libgrimoire.uncompress.$$"

      message "${SPELL_COLOR}${SPELL}: ${QUERY_COLOR}doesn't have an"  \
              "MD5 sum for the uncompressed $1."

      case "$MD5SUM_DL" in
                 off)  message "${RED}Continuing!${DEFAULT_COLOR}"; return 0 ;;
          ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
 ask_risky|ask_abort)  query "Abort?" "y"         && return 1   ||  return 0 ;;
      on|abort_all|*)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
      esac

    elif [[ $2 == "IGNORE" ]] ; then

      rm "$TMP_DIR/libgrimoire.uncompress.$$"

      message "${SPELL_COLOR}${SPELL}: ${QUERY_COLOR}MD5 sum was"  \
              "purposefully left out for the uncompressed $1."
      message "${QUERY_COLOR}Would you like to abort so you can validate"  \
              "the source yourself via some alternate method?"

      case "$MD5SUM_DL" in
                 off)  message "${RED}Continuing!${DEFAULT_COLOR}"; return 0 ;;
ask_risky|ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
           abort_all)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
      ask_abort|on|*)  query "Abort?" "y"         && return 1   ||  return 0 ;;
      esac

    elif [[  "$(cat $TMP_DIR/libgrimoire.uncompress.$$)" != 0  ]]  ; then

      rm "$TMP_DIR/libgrimoire.uncompress.$$"

      message "${SPELL_COLOR}${SPELL}: ${QUERY_COLOR}MD5 sum is different"  \
              "for uncompressed $1."

      case "$MD5SUM_DL" in
                 off)  message "${RED}Continuing!${DEFAULT_COLOR}"; return 0 ;;
          ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
 ask_risky|ask_abort)  query "Abort?" "y"         && return 1   ||  return 0 ;;
      on|abort_all|*)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
      esac

    fi

    rm "$TMP_DIR/libgrimoire.uncompress.$$"

  }

  #By this point, the archive is unarchived, and we know the MD5 check was good.
  return 0

}


#---------------------------------------------------------------------
## @param filename 
## @param compressor 
## @param md5 
## @Stdout uncompressed
##
## Uncompress_md5 dumps the expanded file via tee to md5_tar_check where it 
## is gobbled up by the bitbucket.  It also dumps the main stream out to 
## stdout.
##
#---------------------------------------------------------------------
function uncompress_md5() {
  debug "libgrimoire" "uncompress_md5 - $*"

  # This is here so Duff's super debugging info doesn't screw the next step up
  set +x 

  # Outer subshell is necessary to redirect stderr to stdout
  (
    uncompress_core "$1" "$2" |
      tee /dev/stderr |
      md5_tar_check "$3" 2>&1 1>/dev/null #we must avoid this printing
  ) 2>&1

  # This temp file is here because this function MUST NOT send
  # anything to stdout or stderr, and upack needs a way to get the success or
  # failure of this function.

  local a="$?"
  [[ $SUPER_DEBUG ]] && set -x  #turn this back on as soon as possible
  echo "$a"  > "$TMP_DIR/libgrimoire.uncompress.$$"
  return "$a"

}


#---------------------------------------------------------------------
## @param md5
##
## Checks that the stdin matches the argument.
## Note that DEBUG output may dissapear if it's /dev/stderr due to
## uncompress' 2>/dev/null.
##
#---------------------------------------------------------------------
function md5_tar_check()  {
  debug "libgrimoire" "md5_tar_check() - Checking MD5 sum"

  local md5

  #Do the md5
  md5="$(md5sum /dev/stdin | awk '{print $1}')"
  debug "libgrimoire" "md5_tar_check() - MD5 of tarball is $md5."
  debug "libgrimoire" "md5_tar_check() - argument received is $1."

  #See if they match
  if [[ $1 == $md5 ]] ; then
    debug "libgrimoire" "md5_tar_check() - MD5 Sum Success ( $1 == $md5 )"
    return 0
  fi

  #See of we need to md5sum it at all
  if [[ ${MD5SUM_DL:-on} == off ]] || ! [[  $1  ]] ; then
    debug "libgrimoire" "md5_tar_check() - Skipping check"
    return 0
  fi

  #If we get here, the md5's don't match, but should.
  debug "libgrimoire" "md5_tar_check() - bad md5"
  return 1

}


#---------------------------------------------------------------------
## @License
##
## This software is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This software is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this software; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##
#---------------------------------------------------------------------
