###############################################################################
#
# pkgr.eagle --
#
# Extensible Adaptable Generalized Logic Engine (Eagle)
# Package Repository Client
#
# Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
#
# See the file "license.terms" for information on usage and redistribution of
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: $
#
###############################################################################

#
# NOTE: Use our own namespace here because even though we do not directly
#       support namespaces ourselves, we do not want to pollute the global
#       namespace if this script actually ends up being evaluated in Tcl.
#
namespace eval ::PackageRepository {
  #
  # NOTE: When running from the source tree, modify the auto-path to include
  #       the "Eagle1.0" directory within externals.
  #
  if {1} then {
    lappend ::auto_path [file join [file dirname [file dirname \
        [file dirname [info script]]]] externals Eagle lib Eagle1.0]
  }

  #
  # NOTE: This package requires several packages from the Eagle core script
  #       library, even when it is being used by native Tcl.  If necessary,
  #       prior to evaluating this file in native Tcl, its auto-path should
  #       be modified to include an "Eagle1.0" directory (i.e. a directory
  #       containing the Eagle core script library files "auxiliary.eagle",
  #       "file1.eagle", and "platform.eagle").
  #
  package require Eagle.Platform
  package require Eagle.Auxiliary
  package require Eagle.File

  #
  # NOTE: This block is intended to be evaluated successfully by native
  #       Tcl only.
  #
  catch {
    ::Eagle::exportAndImportPackageCommands ::Eagle [list \
        appendArgs getDictionaryValue isEagle readFile writeFile] \
        false false
  }

  #
  # NOTE: This procedure returns a formatted, possibly version-specific,
  #       package name, for use in logging.
  #
  proc formatPackageName { package version } {
    return [string trim [appendArgs $package " " $version]]
  }

  #
  # NOTE: This procedure returns a formatted script result.  If the string
  #       result is empty, only the return code is used.  The code argument
  #       must be an integer Tcl return code (e.g. from [catch]) and the
  #       result argument is the script result or error message.
  #
  proc formatResult { code result } {
    switch -exact -- $code {
      0 {set codeString ok}
      1 {set codeString error}
      2 {set codeString return}
      3 {set codeString break}
      4 {set codeString continue}
      default {set codeString [appendArgs unknown( $code )]}
    }

    if {[string length $result] > 0} then {
      return [appendArgs $codeString ": " [list $result]]
    } else {
      return $codeString
    }
  }

  #
  # NOTE: This procedure emits a message to the package repository client
  #       log.  The string argument is the content of the message to emit.
  #
  proc pkgLog { string } {
    catch {
      tclLog [appendArgs [pid] " : " [clock seconds] " : pkgr : " $string]
    }
  }

  #
  # NOTE: This procedure attempts to determine if a string is a valid list
  #       and returns non-zero when that is true.  The value argument is
  #       the string to check.
  #
  proc stringIsList { value } {
    if {[isEagle]} then {
      return [string is list $value]
    } else {
      global tcl_version

      if {[info exists tcl_version] && $tcl_version >= 8.5} then {
        return [string is list $value]
      } elseif {[catch {llength $value}] == 0} then {
        return true
      } else {
        return false
      }
    }
  }

  #
  # NOTE: This procedure returns non-zero if the specified string value
  #       looks like a Harpy (script) certificate.  The value argument
  #       is the string to check.
  #
  # <public>
  proc isHarpyCertificate { value } {
    if {[string length $value] == 0 || ([string first [string trim {
      <?xml version="1.0" encoding="utf-8"?>
    }] $value] == 0 && [string first [string trim {
      <Certificate xmlns="https://eagle.to/2011/harpy"
    }] $value] != -1)} then {
      return true
    } else {
      return false
    }
  }

  #
  # NOTE: This procedure returns non-zero if the specified string value
  #       looks like an OpenPGP signature.  The value argument is the
  #       string to check.
  #
  # <public>
  proc isPgpSignature { value } {
    if {[string length $value] == 0 || [string first [string trim {
      -----BEGIN PGP SIGNATURE-----
    }] $value] == 0} then {
      return true
    } else {
      return false
    }
  }

  #
  # NOTE: This procedure returns the fully qualified name of the directory
  #       where temporary files should be written.  The envVarName argument
  #       is an optional extra environment variable to check (first).
  #
  # <public>
  proc getFileTempDirectory { {envVarName ""} } {
    global env

    if {[string length $envVarName] > 0 && \
        [info exists env($envVarName)]} then {
      return $env($envVarName)
    } elseif {[info exists env(TEMP)]} then {
      return $env(TEMP)
    } elseif {[info exists env(TMP)]} then {
      return $env(TMP)
    } else {
      error [appendArgs \
          "please set " $envVarName \
          " (via environment) to temporary directory"]
    }
  }

  #
  # NOTE: This procedure returns a unique temporary file name.  A script
  #       error is raised if this task cannot be accomplished.  There are
  #       no arguments.
  #
  proc getFileTempName {} {
    if {[isEagle]} then {
      return [file tempname]
    } else {
      set directory [getFileTempDirectory PKGR_TEMP]
      set counter [expr {[pid] ^ int(rand() * 0xFFFF)}]

      while {1} {
        set fileNameOnly [format tcl%04X.tmp $counter]
        set fileName [file join $directory $fileNameOnly]

        if {![file exists $fileName]} then {
          return $fileName
        }

        incr counter
      }
    }
  }

  #
  # NOTE: This procedure attempts to verify the OpenPGP signature contained
  #       in the specified (named) file.  Non-zero is only returned if the
  #       OpenPGP signature is verified successfully.  A script error should
  #       not be raised by this procedure.  The fileName argument must be
  #       the fully qualified path and file name of the OpenPGP signature
  #       file to verify.
  #
  # <public>
  proc verifyPgpSignature { fileName } {
    variable pgpCommand

    if {[isEagle]} then {
      set fileName [appendArgs \" $fileName \"]

      if {[catch {
        eval exec -success Success [subst $pgpCommand]
      }] == 0} then {
        return true
      }
    } else {
      if {[catch {
        eval exec [subst $pgpCommand] 2>@1
      }] == 0} then {
        return true
      }
    }

    return false
  }

  #
  # NOTE: This procedure returns the prefix for fully qualified variable
  #       names that MAY be present in the global namespace.  There are
  #       no arguments.
  #
  proc getLookupVarNamePrefix {} {
    return ::pkgr_; # TODO: Make non-global?
  }

  #
  # NOTE: This procedure returns a name suffix (directory, variable, etc)
  #       that is unique to the running process at the current point in
  #       time.  It is used (internally) to avoid name collisions with any
  #       preexisting variables or commands that may be present in the
  #       global namespace.  The paranoia argument represents the relative
  #       level of paranoia required by the caller; the higher this level,
  #       the more uniqueness is required.
  #
  # <public>
  proc getUniqueSuffix { {paranoia 1} } {
    set result [string trim [pid] -]

    if {$paranoia > 0} then {
      append result _ [string trim [clock seconds] -]
    }

    if {$paranoia > 1} then {
      append result _ [string trim \
          [clock clicks -milliseconds] -]; # TODO: Bad?
    }

    return $result
  }

  #
  # NOTE: This procedure returns the list of API keys to use when looking
  #       up packages via the package repository server.  An empty list
  #       is returned if no API keys are currently configured.  The prefix
  #       argument is an extra variable name prefix to check prior to any
  #       that are already configured.
  #
  # <internal>
  proc getApiKeys { {prefix ""} } {
    global env

    set prefixes [list]

    if {[string length $prefix] > 0} then {
      lappend prefixes $prefix
    }

    lappend prefixes [getLookupVarNamePrefix]

    foreach prefix $prefixes {
      if {[string length $prefix] == 0} then {
        set prefix ::; # TODO: Make non-global?
      }

      set varName [appendArgs $prefix api_keys]

      if {[info exists $varName]} then {
        return [set $varName]
      }

      set varName [string trim $varName :]

      if {[info exists env($varName)]} then {
        return $env($varName)
      }
    }

    return [list]; # NOTE: System default, which is "public-only".
  }

  #
  # NOTE: This procedure returns the base URI for the package repository
  #       server.  There are no arguments.
  #
  proc getLookupBaseUri {} {
    set varName [appendArgs [getLookupVarNamePrefix] base_uri]

    if {[info exists $varName]} then {
      return [set $varName]
    }

    global env
    set varName [string trim $varName :]

    if {[info exists env($varName)]} then {
      return $env($varName)
    }

    return https://urn.to/r/pkg; # NOTE: System default.
  }

  #
  # NOTE: This procedure returns the full URI to use when looking up a
  #       specific package via the package repository server.  The apiKey
  #       argument is the API key to use -OR- an empty string if a public
  #       package is being looked up.  The package argument is the name
  #       of the package being looked up, it cannot be an empty string.
  #       The version argument is the specific version being looked up
  #       -OR- an empty string for any available version.  No HTTP request
  #       is issued by this procedure; it just returns the URI to use.
  #
  proc getLookupUri { apiKey package version } {
    set baseUri [getLookupBaseUri]

    if {[string length $baseUri] == 0} then {
      return ""
    }

    #
    # NOTE: Build the HTTP request URI using the specified query parameter
    #       values, escaping them as necessary.  Also, include the standard
    #       query parameters with constant values for this request type.
    #
    if {[isEagle]} then {
      return [appendArgs \
          $baseUri ?raw=1&method=lookup&apiKey= [uri escape uri $apiKey] \
          &package= [uri escape uri $package] &version= [uri escape uri \
          $version]]
    } else {
      package require http 2.0

      return [appendArgs \
          $baseUri ? [http::formatQuery raw 1 method lookup apiKey $apiKey \
          package $package version $version]]
    }
  }

  #
  # NOTE: This procedure returns the version of the package that should be
  #       used to lookup the associated [package ifneeded] script -OR- an
  #       empty string if no such version exists.  The package argument is
  #       the name of the package, it cannot be an empty string.  The
  #       version argument is the specific version being looked up -OR- an
  #       empty string for any available version.
  #
  proc getIfNeededVersion { package version } {
    if {[string length $version] > 0} then {
      return $version
    }

    return [lindex [package versions $package] 0]
  }

  #
  # NOTE: This procedure accepts a package requirement (spec) and returns
  #       a simple package version, if possible.  An empty string will be
  #       returned, if appropriate (i.e. any version should be allowed).
  #       The requirement argument must be a package specification that
  #       conforms to TIP #268.
  #
  proc packageRequirementToVersion { requirement } {
    set result $requirement

    if {[set index [string first - $result]] != -1} then {
      incr index -1; set result [string range $result 0 $index]
    }

    if {[set index [string first a $result]] != -1 || \
        [set index [string first b $result]] != -1} then {
      incr index -1; set result [string range $result 0 $index]
    }

    if {$result eq "0"} then {
      set result ""
    } elseif {[regexp -- {^\d+$} $result]} then {
      append result .0
    }

    return $result
  }

  #
  # NOTE: This procedure issues an HTTP request that should return metadata
  #       that can be used to load and/or provide the specified package.
  #       The apiKey argument is the API key to use -OR- an empty string if
  #       a public package is being looked up.  The package argument is the
  #       name of the package, it cannot be an empty string.  The version
  #       argument is the specific version being looked up -OR- an empty
  #       string for any available version.  This procedure may raise script
  #       errors.  All line-endings are normalized to Unix-style; therefore,
  #       all script signatures must assume this.
  #
  proc getLookupData { apiKey package version } {
    variable verboseUriDownload

    set uri [getLookupUri $apiKey $package $version]

    if {[string length $uri] == 0} then {
      return ""
    }

    if {$verboseUriDownload} then {
      pkgLog [appendArgs \
          "attempting to download URI \"" $uri \"...]
    }

    if {[isEagle]} then {
      set data [uri download -inline $uri]
    } else {
      set data [getFileViaHttp \
          $uri 20 stdout [expr {!$verboseUriDownload}] -binary true]
    }

    if {$verboseUriDownload} then {
      pkgLog [appendArgs \
          "raw response data is: " $data]
    }

    set data [string map [list &lt\; < &gt\; > &quot\; \" &amp\; &] $data]
    set data [string map [list \r\n \n \r \n] $data]
    set data [string trim $data]

    return $data
  }

  #
  # NOTE: This procedure attempts to extract the lookup code from the raw
  #       HTTP response data.  The data argument is the raw HTTP response
  #       data.  An empty string is returned if no lookup code is available.
  #
  proc getLookupCodeFromData { data } {
    if {![stringIsList $data] || [llength $data] < 1} then {
      return ""
    }

    return [lindex $data 0]
  }

  #
  # NOTE: This procedure attempts to extract the lookup result from the raw
  #       HTTP response data.  The data argument is the raw HTTP response
  #       data.  An empty string is returned if no lookup result is available.
  #
  proc getLookupResultFromData { data } {
    if {![stringIsList $data] || [llength $data] < 2} then {
      return ""
    }

    return [lindex $data 1]
  }

  #
  # NOTE: This procedure returns non-zero if the specified lookup response
  #       code indicates success.  The code argument is the extracted HTTP
  #       lookup response code.
  #
  proc isLookupCodeOk { code } {
    #
    # NOTE: The code must be the literal string "OK" for the package lookup
    #       request to be considered successful.
    #
    return [expr {$code eq "OK"}]
  }

  #
  # NOTE: This procedure was stolen from the "common.tcl" script used by the
  #       package repository server.  It has been modified to support both
  #       native Tcl and Eagle.  It should be noted here that TIP #268 syntax
  #       is not supported by Eagle.  For native Tcl, the requirement argument
  #       must be a package version or requirement conforming to the TIP #268
  #       syntax.  For Eagle, the requirement argument must be a simple dotted
  #       package version, with up to four components, without any 'a' or 'b'.
  #       The emptyOk argument should be non-zero if an empty string should be
  #       considered to be valid by the caller.  The rangeOk argument should
  #       be non-zero if the version range syntax is allowed; this argument is
  #       ignored for Eagle because it requires TIP #268 support.
  #
  proc isValidPackageRequirement { requirement rangeOk {emptyOk false} } {
    if {$emptyOk && [string length $requirement] == 0} then {
      return true
    }

    if {[isEagle]} then {
      #
      # NOTE: Eagle does not support TIP #268.  Use the built-in sub-command
      #       that checks a version number.
      #
      return [string is version -strict $requirement]
    } else {
      #
      # HACK: If a version range is not allowed, make sure that the dash
      #       character is not present.
      #
      if {!$rangeOk && [string first - $requirement] != -1} then {
        return false
      }

      #
      # HACK: There is no direct way to check if a package requirement
      #       that uses the TIP #268 syntax is valid; however, we can
      #       purposely "misuse" the [package present] command for this
      #       purpose.  We know the "Tcl" package is always present;
      #       therefore, if an error is raised here, then the package
      #       requirement is probably invalid.  Unfortunately, the error
      #       message text has to be checked as well; otherwise, there
      #       is no way to verify version numbers that happen to be less
      #       than the running patch level of Tcl.
      #
      if {[catch {package present Tcl $requirement} error] == 0} then {
        return true
      } else {
        #
        # TODO: Maybe this will require updates in the future?
        #
        set pattern(1) "expected version number but got *"
        set pattern(2) "expected versionMin-versionMax but got *"

        if {![string match $pattern(1) $error] && \
            ![string match $pattern(2) $error]} then {
          return true
        } else {
          return false
        }
      }
    }
  }

  #
  # NOTE: This procedure attempts to extract the package lookup metadata from
  #       the lookup result.  The result argument is the lookup result.  The
  #       varName argument is the name of an array variable, in the call frame
  #       of the immediate caller, that should receive the extracted package
  #       lookup metadata.  The caller argument must be an empty string -OR-
  #       the literal string "handler".
  #
  proc extractAndVerifyLookupMetadata { result varName caller } {
    variable strictUnknownLanguage

    #
    # NOTE: Grab the returned patch level.  It cannot be an empty string
    #       and it must conform to the TIP #268 requirements for a single
    #       package version.
    #
    set patchLevel [getDictionaryValue $result PatchLevel]

    if {[string length $patchLevel] == 0} then {
      error "missing patch level"
    }

    if {![isValidPackageRequirement $patchLevel false]} then {
      error "bad patch level"
    }

    #
    # NOTE: Grab the language for the package script.  It must be an empty
    #       string, "Tcl", or "Eagle".  If it is an empty string, "Eagle"
    #       will be assumed.
    #
    set language [getDictionaryValue $result Language]

    if {[lsearch -exact [list "" Tcl Eagle] $language] == -1} then {
      error "unsupported language"
    }

    #
    # NOTE: Grab the package script.  If it is an empty string, then the
    #       package cannot be loaded and there is nothing to do.  In that
    #       case, just raise an error.
    #
    set script [getDictionaryValue $result Script]

    if {[string length $script] == 0} then {
      error "missing script"
    }

    #
    # NOTE: Grab the package script certificate.  If it is an empty string
    #       then the package script is unsigned, which is not allowed by
    #       this client.  In that case, just raise an error.
    #
    set certificate [getDictionaryValue $result Certificate]

    if {[string length $certificate] == 0} then {
      error "missing script certificate"
    }

    #
    # NOTE: Are we being called from the [package unknown] handler
    #       in "strict" mode?
    #
    if {$strictUnknownLanguage && $caller eq "handler"} then {
      #
      # NOTE: If so, the package script must be targeted at the this
      #       language; otherwise, there exists the possibility that
      #       the package may not be provided to this language.
      #
      if {[isEagle]} then {
        if {$language ne "Eagle"} then {
          error "repository package is not for Eagle"
        }
      } else {
        if {$language ne "Tcl"} then {
          error "repository package is not for Tcl"
        }
      }
    }

    #
    # NOTE: If the caller wants the package lookup metadata, use their
    #       array variable name.
    #
    if {[string length $varName] > 0} then {
      upvar 1 $varName metadata

      set metadata(patchLevel) $patchLevel
      set metadata(language) $language
      set metadata(script) $script
      set metadata(certificate) $certificate
    }
  }

  #
  # NOTE: This procedure, which may only be used from an Eagle script, checks
  #       if a native Tcl library is loaded and ready.  If not, a script error
  #       is raised.
  #
  proc tclMustBeReady {} {
    #
    # NOTE: This procedure is not allowed to actually load a native Tcl
    #       library; therefore, one must already be loaded.
    #
    if {![isEagle]} then {
      error "already running in Tcl language"
    }

    if {![tcl ready]} then {
      error "cannot use Tcl language, supporting library is not loaded"
    }
  }

  #
  # NOTE: This procedure, which may only be used from a native Tcl script,
  #       checks if Garuda and Eagle are loaded and ready.  If not, a script
  #       error is raised.
  #
  proc eagleMustBeReady {} {
    #
    # NOTE: This procedure is not allowed to actually load Garuda (and
    #       Eagle); therefore, they must already be loaded.
    #
    if {[isEagle]} then {
      error "already running in Eagle language"
    }

    if {[llength [info commands eagle]] == 0} then {
      error "cannot use Eagle language, supporting package is not loaded"
    }
  }

  #
  # NOTE: This procedure returns non-zero if the current script is being
  #       evaluated in Eagle with signed-only script security enabled.
  #       There are no arguments.
  #
  proc eagleHasSecurity {} {
    #
    # NOTE: If possible, check if the current interpreter has security
    #       enabled.
    #
    if {[isEagle] && [llength [info commands object]] > 0} then {
      if {[catch {
        object invoke -flags +NonPublic Interpreter.GetActive HasSecurity
      } security] == 0 && $security} then {
        return true
      }
    }

    return false
  }

  #
  # NOTE: This procedure uses the package lookup metadata.  If the package
  #       script is properly signed, an attempt will be made to evaluate it
  #       in the target language.  If the script was signed using OpenPGP,
  #       then a conforming implementation of the OpenPGP specification (e.g.
  #       gpg2) must be installed locally.  If the script was signed using
  #       Harpy then Garuda, Eagle, and Harpy must be installed locally.
  #       This procedure is designed to work for both native Tcl and Eagle
  #       packages.  Additionally, it is designed to work when evaluated
  #       using either native Tcl or Eagle; however, it is up to the package
  #       script itself to either add the package or provide the package to
  #       the language(s) supported by that package.  The varName argument
  #       is the name of an array variable in the call frame of the
  #       immediate caller, that contains the package lookup metadata.  This
  #       procedure may raise script errors.
  #
  proc processLookupMetadata { varName } {
    #
    # NOTE: If the metadata variable name appears to be invalid, fail.
    #
    if {[string length $varName] == 0} then {
      error "bad metadata"
    }

    #
    # NOTE: This procedure requires that the metadata array variable is
    #       present in the call frame immediately above this one.
    #
    upvar 1 $varName metadata

    #
    # NOTE: If the entire package metadata array is missing, fail.
    #
    if {![info exists metadata]} then {
      error "missing metadata"
    }

    #
    # NOTE: If the patch level for the package is mising, fail.
    #
    if {![info exists metadata(patchLevel)]} then {
      error "missing patch level"
    }

    #
    # NOTE: If the language for the package script is mising, fail.
    #
    if {![info exists metadata(language)]} then {
      error "missing language"
    }

    #
    # NOTE: If the package script is mising, fail.
    #
    if {![info exists metadata(script)]} then {
      error "missing script"
    }

    #
    # NOTE: If the package script certificate is mising, fail.
    #
    if {![info exists metadata(certificate)]} then {
      error "missing script certificate"
    }

    #
    # NOTE: Create common cleanup script block that deletes any temporary
    #       files created for the script verification process.
    #
    set script(cleanup) {
      if {[string length $fileName(2)] > 0 && \
          [file exists $fileName(2)] && [file isfile $fileName(2)]} then {
        if {![info exists ::env(pkgr_keep_files)]} then {
          catch {file delete $fileName(2)}
        }
        unset -nocomplain fileName(2)
      }

      if {[string length $fileName(1)] > 0 && \
          [file exists $fileName(1)] && [file isfile $fileName(1)]} then {
        if {![info exists ::env(pkgr_keep_files)]} then {
          catch {file delete $fileName(1)}
        }
        unset -nocomplain fileName(1)
      }
    }

    #
    # NOTE: Figure out the "type" of script certificate we are now dealing
    #       with.
    #
    if {[isHarpyCertificate $metadata(certificate)]} then {
      #
      # NOTE: Attempt to create a completely unique array variable name to
      #       hold the package metadata in this scripting language as well
      #       as possibly in the other necessary scripting language(s).
      #
      set newVarName(1) [appendArgs \
          [getLookupVarNamePrefix] metadata_ [getUniqueSuffix 2]]

      set newVarName(2) [appendArgs \
          [getLookupVarNamePrefix] cleanup_ [getUniqueSuffix 2]]

      set newProcName(1) [appendArgs \
          [getLookupVarNamePrefix] eagleHasSecurity_ [getUniqueSuffix 2]]

      set newProcName(2) [appendArgs \
          [getLookupVarNamePrefix] getFileTempName_ [getUniqueSuffix 2]]

      set newProcName(3) [appendArgs \
          [getLookupVarNamePrefix] tclMustBeReady_ [getUniqueSuffix 2]]

      #
      # NOTE: Create the Eagle script block that will be used to securely
      #       evaluate a signed package script.  This must be evaluated in
      #       Eagle because it uses several plugins only available there.
      #
      set script(outer) [string map [list \
          %metadata% $newVarName(1) %cleanup% $newVarName(2) \
          %eagleHasSecurity% $newProcName(1) %getFileTempName% \
          $newProcName(2) %tclMustBeReady% $newProcName(3)] {
        try {
          #
          # NOTE: If there is no package script, there is nothing we
          #       can do here.
          #
          if {[string length ${%metadata%(script)}] > 0} then {
            #
            # NOTE: Save the security state for the interpreter.  Then, attempt
            #       to enable it.  This will fail if one of the needed plugins
            #       cannot be loaded.
            #
            set savedSecurity [{%eagleHasSecurity%}]
            if {!$savedSecurity} then {source enableSecurity}

            try {
              #
              # NOTE: Figure out temporary file name for the downloaded script
              #       and its associated script certificate.
              #
              set fileName(1) [{%getFileTempName%}]
              set fileName(2) [appendArgs $fileName(1) .harpy]

              try {
                #
                # NOTE: Write downloaded script to a temporary file.
                #
                writeFile $fileName(1) ${%metadata%(script)}

                #
                # NOTE: Write downloaded script certificate to a temporary
                #       file.
                #
                if {[string length ${%metadata%(certificate)}] > 0} then {
                  writeFile $fileName(2) ${%metadata%(certificate)}
                }

                #
                # NOTE: This seems stupid.  Why are we reading the downloaded
                #       script from the temporary file when we already had it
                #       in memory?  The reason is that we need to make sure
                #       that the Harpy policy engine has a chance to check the
                #       downloaded script against its associated certificate.
                #       This will raise a script error if the script signature
                #       is missing or invalid.
                #
                set script(inner) [interp readorgetscriptfile -- \
                    "" $fileName(1)]

                #
                # NOTE: Determine the target language for the package script,
                #       which may or may not be the language that is currently
                #       evaluating this script (Eagle).  The default language,
                #       when one was not explicitly specified, is Eagle.  In
                #       the future, this may be changed, e.g. to use the file
                #       extension of the client script.
                #
                switch -exact -- ${%metadata%(language)} {
                  "" -
                  Eagle {
                    #
                    # NOTE: The target language is Eagle, which is evaluating
                    #       this script.  No special handling is needed here.
                    #
                    return [uplevel #0 $script(inner)]
                  }
                  Tcl {
                    #
                    # NOTE: The target language is Tcl; therefore, a bit of
                    #       special handling is needed here.
                    #
                    {%tclMustBeReady%}; return [tcl eval [tcl master] [list \
                        uplevel #0 $script(inner)]]
                  }
                  default {
                    error "unsupported language"
                  }
                }
              } finally {
                #
                # NOTE: Perform any necessary cleanup steps.
                #
                eval ${%cleanup%}
              }
            } finally {
              #
              # NOTE: Restore the saved security state for the interpreter.
              #
              if {!$savedSecurity} then {source disableSecurity}
              unset -nocomplain savedSecurity
            }
          }
        } finally {
          rename {%tclMustBeReady%} ""
          rename {%getFileTempName%} ""
          rename {%eagleHasSecurity%} ""

          unset -nocomplain {%cleanup%}
          unset -nocomplain {%metadata%}
        }
      }]

      #
      # NOTE: Copy the package metadata into the fresh array variable,
      #       if necessary, marshalling it from native Tcl to Eagle.
      #
      if {[isEagle]} then {
        array set $newVarName(1) [array get metadata]
        set $newVarName(2) $script(cleanup)

        proc $newProcName(1) {} [info body [appendArgs \
            [namespace current] ::eagleHasSecurity]]

        proc $newProcName(2) {} [info body [appendArgs \
            [namespace current] ::getFileTempName]]

        proc $newProcName(3) {} [info body [appendArgs \
            [namespace current] ::tclMustBeReady]]

        return [eval $script(outer)]
      } else {
        eagleMustBeReady

        eagle [list array set $newVarName(1) [array get metadata]]
        eagle [list set $newVarName(2) $script(cleanup)]

        eagle [list proc $newProcName(1) {} [info body [appendArgs \
            [namespace current] ::eagleHasSecurity]]]

        eagle [list proc $newProcName(2) {} [info body [appendArgs \
            [namespace current] ::getFileTempName]]]

        eagle [list proc $newProcName(3) {} [info body [appendArgs \
            [namespace current] ::tclMustBeReady]]]

        return [eagle $script(outer)]
      }
    } elseif {[isPgpSignature $metadata(certificate)]} then {
      #
      # NOTE: If there is no package script, there is nothing we
      #       can do here.
      #
      if {[string length $metadata(script)] > 0} then {
        #
        # NOTE: Figure out temporary file name for the downloaded script
        #       and its associated OpenPGP signature.
        #
        set fileName(1) [getFileTempName]
        set fileName(2) [appendArgs $fileName(1) .asc]

        #
        # NOTE: Write downloaded script to a temporary file.
        #
        writeFile $fileName(1) $metadata(script)

        #
        # NOTE: Write downloaded script OpenPGP signature a temporary file.
        #
        if {[string length $metadata(certificate)] > 0} then {
          writeFile $fileName(2) $metadata(certificate)
        }

        #
        # NOTE: Attempt to verify the OpenPGP signature for the package
        #       script.
        #
        if {[verifyPgpSignature $fileName(2)]} then {
          #
          # NOTE: Delete the temporary files that we created for the
          #       OpenPGP signature verification.
          #
          eval $script(cleanup)
        } else {
          #
          # NOTE: Delete the temporary files that we created for the
          #       OpenPGP signature verification.
          #
          eval $script(cleanup)

          #
          # NOTE: OpenPGP signature verification failed.  Raise an error
          #       and do not proceed with evaluating the package script.
          #
          error "bad PGP signature"
        }

        #
        # NOTE: The OpenPGP signature was verified; use the downloaded
        #       package script verbatim.
        #
        set script(inner) $metadata(script)

        #
        # NOTE: Determine the target language for the package script, which
        #       may or may not be the language that is currently evaluating
        #       this script (Eagle).  The default language, when one was not
        #       explicitly specified, is Eagle.  In the future, this may be
        #       changed, e.g. to use the file extension of the client script.
        #
        switch -exact -- $metadata(language) {
          "" -
          Eagle {
            if {[isEagle]} then {
              return [uplevel #0 $script(inner)]
            } else {
              eagleMustBeReady

              return [eagle [list uplevel #0 $script(inner)]]
            }
          }
          Tcl {
            if {[isEagle]} then {
              tclMustBeReady; return [tcl eval [tcl master] [list \
                  uplevel #0 $script(inner)]]
            } else {
              return [uplevel #0 $script(inner)]
            }
          }
          default {
            error "unsupported language"
          }
        }
      }
    } else {
      error "unsupported script certificate"
    }
  }

  #
  # NOTE: This procedure performs initial setup of the package repository
  #       client, using the current configuration parameters.  There are
  #       no arguments.  It may load the Garuda package when evaluated in
  #       native Tcl.  It may load a native Tcl library when evaluated in
  #       Eagle.  It may install the [package unknown] hook.
  #
  proc setupPackageUnknownHandler {} {
    variable autoHook
    variable autoLoadTcl
    variable autoRequireGaruda

    if {$autoRequireGaruda && ![isEagle]} then {
      #
      # TODO: Assume this package is trusted?  How can we verify it
      #       at this point?
      #
      package require Garuda
    }

    if {$autoLoadTcl && [isEagle]} then {
      #
      # NOTE: Load a native Tcl library.  It must be signed with a valid
      #       Authenticode signature.
      #
      tcl load -findflags +TrustedOnly -loadflags +SetDllDirectory
    }

    if {$autoHook && ![isPackageUnknownHandlerHooked]} then {
      #
      # NOTE: Install our [package unknown] handler and save the original
      #       one for our use as well.
      #
      hookPackageUnknownHandler
    }
  }

  #
  # NOTE: This procedure returns non-zero if the [package unknown] handler
  #       has already been hooked by the package repository client.  There
  #       are no arguments.
  #
  proc isPackageUnknownHandlerHooked {} {
    return [info exists [appendArgs \
        [getLookupVarNamePrefix] saved_package_unknown]]
  }

  #
  # NOTE: This procedure attempts to hook the [package unknown] handler.  It
  #       will raise a script error if this has already been done.  The old
  #       [package unknown] handler is saved and will be used by the new one
  #       as part of the overall package loading process.  There are no
  #       arguments.
  #
  proc hookPackageUnknownHandler {} {
    set varName [appendArgs [getLookupVarNamePrefix] saved_package_unknown]

    if {[info exists $varName]} then {
      error "package unknown handler already hooked"
    }

    set $varName [package unknown]
    package unknown [appendArgs [namespace current] ::packageUnknownHandler]
  }

  #
  # NOTE: This procedure attempts to unhook the [package unknown] handler.
  #       It will raise a script error if the [package unknown] handler is
  #       not hooked.  The old [package unknown] handler is restored and
  #       the saved [package unknown] handler is cleared.  There are no
  #       arguments.
  #
  proc unhookPackageUnknownHandler {} {
    set varName [appendArgs [getLookupVarNamePrefix] saved_package_unknown]

    if {![info exists $varName]} then {
      error "package unknown handler is not hooked"
    }

    package unknown [set $varName]
    unset $varName
  }

  #
  # NOTE: The procedure runs the saved [package unknown] handler.  Any script
  #       errors are raised to the caller.  The package and version arguments
  #       are passed in from the current [package unknown] handler verbatim.
  #
  proc runSavedPackageUnknownHandler { package version } {
    #
    # NOTE: See if there is a saved [package unknown] handler.  If so, then
    #       attempt to use it.
    #
    set varName [appendArgs [getLookupVarNamePrefix] saved_package_unknown]
    set oldHandler [expr {[info exists $varName] ? [set $varName] : ""}]

    if {[string length $oldHandler] > 0} then {
      lappend oldHandler $package $version; uplevel #0 $oldHandler
    }
  }

  #
  # NOTE: This procedure is the [package unknown] handler entry point called
  #       by native Tcl and Eagle.  The package argument is the name of the
  #       package being sought, it cannot be an empty string.  The version
  #       argument must be a specific version -OR- a package specification
  #       that conforms to TIP #268.  This version argument must be optional
  #       here, because Eagle does not add a version argument when one is
  #       not explicitly supplied to the [package require] sub-command.
  #
  proc packageUnknownHandler { package {version ""} } {
    variable verboseUnknownResult

    #
    # NOTE: First, run our special [package unknown] handler.
    #
    set code(1) [catch {
      getPackageFromRepository $package $version handler
    } result(1)]

    if {$verboseUnknownResult} then {
      pkgLog [appendArgs \
          "repository handler results for package \"" [formatPackageName \
          $package $version] "\" are " [formatResult $code(1) $result(1)]]
    }

    #
    # NOTE: Next, run the saved [package unknown] handler.
    #
    set code(2) [catch {
      runSavedPackageUnknownHandler $package $version
    } result(2)]

    if {$verboseUnknownResult} then {
      pkgLog [appendArgs \
          "saved handler results for package \"" [formatPackageName \
          $package $version] "\" are " [formatResult $code(2) $result(2)]]
    }

    #
    # NOTE: Maybe check for the package and then optionally log results.
    #
    if {$verboseUnknownResult} then {
      set ifNeededVersion [getIfNeededVersion \
          $package [packageRequirementToVersion $version]]

      if {[string length $ifNeededVersion] > 0} then {
        set command [list package ifneeded $package $ifNeededVersion]

        if {[catch $command result(3)] == 0 && \
            [string length $result(3)] > 0} then {
          pkgLog [appendArgs \
              "package script for \"" [formatPackageName $package \
              $ifNeededVersion] "\" was added: " [list $result(3)]]
        } else {
          pkgLog [appendArgs \
              "package script for \"" [formatPackageName $package \
              $ifNeededVersion] "\" was not added: " [list $result(3)]]
        }
      } else {
        pkgLog [appendArgs \
            "package script for \"" [formatPackageName $package \
            $ifNeededVersion] "\" was not added"]
      }

      set command [list package present $package]
      if {[string length $version] > 0} then {lappend command $version}

      if {[catch $command] == 0} then {
        pkgLog [appendArgs \
            "package \"" [formatPackageName $package $version] \
            "\" was loaded"]
      } else {
        pkgLog [appendArgs \
            "package \"" [formatPackageName $package $version] \
            "\" was not loaded"]
      }
    }
  }

  #
  # NOTE: This procedure evaluates the package repository client settings
  #       script file, if it exists.  Any script errors raised are not
  #       masked.  The script argument must be the fully qualified path
  #       and file name for the primary package repository client script
  #       file.
  #
  # <public>
  proc maybeReadSettingsFile { script } {
    global tcl_platform

    if {[string length $script] == 0 || \
        ![file exists $script] || ![file isfile $script]} then {
      return
    }

    foreach prefix [list $tcl_platform(user) ""] {
      if {[string length $prefix] > 0} then {
        set prefix [appendArgs . $prefix]
      }

      set fileName [appendArgs \
          [file rootname $script] .settings $prefix [file extension \
          $script]]

      if {[file exists $fileName] && [file isfile $fileName]} then {
        uplevel 1 [list source $fileName]
      }
    }
  }

  #
  # NOTE: This procedure sets up the default values for all configuration
  #       parameters used by the package repository client.  There are no
  #       arguments.
  #
  proc setupPackageUnknownVars {} {
    #
    # NOTE: Automatically install our [package unknown] handler when this
    #       package is loaded?
    #
    variable autoHook; # DEFAULT: true

    if {![info exists autoHook]} then {
      set autoHook true
    }

    #
    # NOTE: Automatically [tcl load] when this package is loaded from the
    #       Eagle language?
    #
    variable autoLoadTcl; # DEFAULT: true

    if {![info exists autoLoadTcl]} then {
      set autoLoadTcl true
    }

    #
    # NOTE: Automatically [package require Garuda] when this package is
    #       loaded from the Tcl language?
    #
    variable autoRequireGaruda; # DEFAULT: true

    if {![info exists autoRequireGaruda]} then {
      set autoRequireGaruda true
    }

    #
    # NOTE: The command to use when verifying OpenPGP signatures for the
    #       downloaded package scripts.
    #
    variable pgpCommand; # DEFAULT: gpg2 --verify {${fileName}}

    if {![info exists pgpCommand]} then {
      set pgpCommand {gpg2 --verify {${fileName}}}
    }

    #
    # NOTE: Verify that the package script matches the current language
    #       when called from the [package unknown] handler?
    #
    variable strictUnknownLanguage; # DEFAULT: true

    if {![info exists strictUnknownLanguage]} then {
      set strictUnknownLanguage true
    }

    #
    # NOTE: Emit diagnostic messages when a [package unknown] handler
    #       is called?
    #
    variable verboseUnknownResult; # DEFAULT: false

    if {![info exists verboseUnknownResult]} then {
      set verboseUnknownResult false
    }

    #
    # NOTE: Emit diagnostic messages when a URI is fetched?
    #
    variable verboseUriDownload; # DEFAULT: false

    if {![info exists verboseUriDownload]} then {
      set verboseUriDownload false
    }
  }

  #
  # NOTE: This procedure is the primary entry point to the package repository
  #       client.  It attempts to lookup the specified package using the
  #       currently configured package repository server.  The package
  #       argument is the name of the package being sought, it cannot be an
  #       empty string.  The version argument must be a specific version -OR-
  #       a package specification that conforms to TIP #268.  The caller
  #       argument must be an empty string -OR- the literal string "handler".
  #
  # <public>
  proc getPackageFromRepository { package version caller } {
    #
    # NOTE: Get the list of API keys and try each one, in order, until
    #       the package is found.
    #
    set apiKeys [getApiKeys]; lappend apiKeys ""

    foreach apiKey $apiKeys {
      #
      # NOTE: Issue the lookup request to the remote package repository.
      #
      set data [getLookupData $apiKey $package $version]

      #
      # NOTE: Attempt to grab the lookup code from the response data.
      #
      set code [getLookupCodeFromData $data]

      #
      # NOTE: Did the lookup operation succeed?  If so, stop trying
      #       other API keys.
      #
      if {[isLookupCodeOk $code]} then {
        break
      }
    }

    #
    # NOTE: Attempt to grab the lookup data from the response data.
    #       Upon failure, this should contain the error message.
    #
    set result [getLookupResultFromData $data]

    #
    # NOTE: Did the lookup operation fail?
    #
    if {![isLookupCodeOk $code]} then {
      #
      # NOTE: Is there an error message?
      #
      if {[string length $result] > 0} then {
        #
        # NOTE: Yes.  Use the returned error message verbatim.
        #
        error $result
      } else {
        #
        # NOTE: No.  Use the whole response data string as the error
        #       message.
        #
        error $data
      }
    }

    #
    # NOTE: Process the lookup data into the pieces of metadata that we
    #       need to load the requested package.
    #
    extractAndVerifyLookupMetadata $result metadata $caller

    #
    # NOTE: Attempt to load the requested package using the metadata
    #       extracted in the previous step.
    #
    processLookupMetadata metadata
  }

  if {![isEagle]} then {
    ###########################################################################
    ############################# BEGIN Tcl ONLY ##############################
    ###########################################################################

    #
    # NOTE: This procedure was stolen from the "getEagle.tcl" script.  It is
    #       designed to emit a progress indicator while an HTTP request is
    #       being processed.  The channel argument is the Tcl channel where
    #       the progress indicator should be emitted.  The type argument is
    #       the single-character progress indicator.  The milliseconds
    #       argument is the number of milliseconds to wait until the next
    #       periodic progress indicator should be emitted.  This procedure
    #       reschedules its own execution.
    #
    proc pageProgress { channel type milliseconds } {
      #
      # NOTE: This variable is used to keep track of the currently scheduled
      #       (i.e. pending) [after] event.
      #
      variable afterForPageProgress

      #
      # NOTE: Show that something is happening...
      #
      catch {puts -nonewline $channel $type; flush $channel}

      #
      # NOTE: Make sure that we are scheduled to run again, if requested.
      #
      if {$milliseconds > 0} then {
        set afterForPageProgress [after $milliseconds \
            [namespace code [list pageProgress $channel $type \
            $milliseconds]]]
      } else {
        unset -nocomplain afterForPageProgress
      }
    }

    #
    # NOTE: This procedure was stolen from the "getEagle.tcl" script.  It is
    #       designed to process a single HTTP request, including any HTTP
    #       3XX redirects (up to the specified limit), and return the raw
    #       HTTP response data.  It does not contain special code to handle
    #       HTTP status codes other than 3XX (e.g. 4XX, 5XX, etc).
    #
    # <public>
    proc getFileViaHttp { uri redirectLimit channel quiet args } {
      #
      # NOTE: This variable is used to keep track of the currently scheduled
      #       (i.e. pending) [after] event.
      #
      variable afterForPageProgress

      #
      # NOTE: This procedure requires the modern version of the HTTP package,
      #       which is typically included with the Tcl core distribution.
      #
      package require http 2.0

      #
      # NOTE: If the 'tls' package is available, always attempt to use HTTPS.
      #
      if {[catch {package require tls}] == 0} then {
        ::http::register https 443 ::tls::socket

        if {[string range $uri 0 6] eq "http://"} then {
          set uri [appendArgs https:// [string range $uri 7 end]]
        }
      }

      #
      # NOTE: Unless the caller forbids it, display progress messages during
      #       the download.
      #
      if {!$quiet} then {
        pageProgress $channel . 250
      }

      #
      # NOTE: All downloads are handled synchronously, which is not ideal;
      #       however, it is simple.  Keep going as long as there are less
      #       than X redirects.
      #
      set redirectCount 0

      while {1} {
        #
        # NOTE: Issue the HTTP request now, grabbing the resulting token.
        #
        set token [eval [list ::http::geturl $uri] $args]

        #
        # NOTE: Check the HTTP response code, in order to follow any HTTP
        #       redirect responses.
        #
        switch -exact -- [http::ncode $token] {
          301 -
          302 -
          303 -
          307 {
            #
            # NOTE: Unless the caller forbids it, display progress messages
            #       when an HTTP redirect is returned.
            #
            if {!$quiet} then {
              pageProgress $channel > 0
            }

            #
            # NOTE: We hit another HTTP redirect.  Stop if there are more
            #       than X.
            #
            incr redirectCount

            #
            # TODO: Maybe make this limit configurable?
            #
            if {$redirectCount > $redirectLimit} then {
              #
              # NOTE: Just "give up" and return whatever data that we have
              #       now.
              #
              set data [::http::data $token]
              ::http::cleanup $token; break
            }

            #
            # NOTE: Grab the metadata associated with this HTTP response.
            #
            array set meta [::http::meta $token]

            #
            # NOTE: Is there actually a new URI (location) to use?
            #
            if {[info exist meta(Location)]} then {
              #
              # NOTE: Ok, grab it now.  Later, at the top of the loop,
              #       it will be used in the subsequent HTTP request.
              #
              set location $meta(Location); unset meta

              #
              # NOTE: For security, do NOT follow an HTTP redirect if
              #       it attempts to redirect from HTTPS to HTTP.
              #
              if {[string range $uri 0 7] eq "https://" && \
                  [string range $location 0 7] ne "https://"} then {
                #
                # NOTE: Just "give up" and return whatever data that
                #       we have now.
                #
                set data [::http::data $token]
                ::http::cleanup $token; break
              }

              #
              # NOTE: Replace the original URI with the new one, for
              #       use in the next HTTP request.
              #
              set uri $location

              #
              # NOTE: Cleanup the current HTTP token now beause a new
              #       one will be created for the next request.
              #
              ::http::cleanup $token
            } else {
              #
              # NOTE: Just "give up" and return whatever data that we
              #       have now.
              #
              set data [::http::data $token]
              ::http::cleanup $token; break
            }
          }
          default {
            #
            # NOTE: Ok, the HTTP response is actual data of some kind
            #       (which may be an error); however, it is not any
            #       kind of supported HTTP redirect.
            #
            set data [::http::data $token]
            ::http::cleanup $token; break
          }
        }
      }

      #
      # NOTE: If there is a currently scheduled [after] event, cancel it.
      #
      if {[info exists afterForPageProgress]} then {
        catch {after cancel $afterForPageProgress}
        unset -nocomplain afterForPageProgress
      }

      #
      # NOTE: If progress messages were emitted, start a fresh line.
      #
      if {!$quiet} then {
        catch {puts $channel [appendArgs " " $uri]; flush $channel}
      }

      return $data
    }

    ###########################################################################
    ############################## END Tcl ONLY ###############################
    ###########################################################################
  }

  #
  # NOTE: Attempt to read optional settings file now.  This may override
  #       one or more of the variable setup in the next step.
  #
  maybeReadSettingsFile [info script]

  #
  # NOTE: Setup the variables, within this namespace, used by this script.
  #
  setupPackageUnknownVars

  #
  # NOTE: Setup for our [package unknown] handler, which may involve a few
  #       different operations.
  #
  setupPackageUnknownHandler

  #
  # NOTE: Provide the package to the interpreter.
  #
  package provide Eagle.Package.Repository \
    [expr {[isEagle] ? [info engine PatchLevel] : "1.0"}]
}
