github.com/stevenmatthewt/agent@v3.5.4+incompatible/env/export.go (about)

     1  package env
     2  
     3  import (
     4  	"regexp"
     5  	"strings"
     6  )
     7  
     8  var posixExportLineRegex = regexp.MustCompile("\\Adeclare \\-x ([a-zA-Z_]+[a-zA-Z0-9_]*)(=\")?(.+)?\\z")
     9  var endsWithUnescapedQuoteRegex = regexp.MustCompile("([^\\\\]\"\\z|\\A\"\\z)")
    10  
    11  // FromExport parses environment variables from a shell export of environment variables. On
    12  // *nix it looks like this:
    13  //
    14  //     $ export -p
    15  //     declare -x USER="keithpitt"
    16  //     declare -x VAR1="boom\\nboom\\nshake\\nthe\\nroom"
    17  //     declare -x VAR2="hello
    18  //     friends"
    19  //     declare -x VAR3="hello
    20  //     friends
    21  //     OMG=foo
    22  //     test"
    23  //     declare -x VAR4="great
    24  //     typeset -x TOTES=''
    25  //     lollies"
    26  //     declare -x XPC_FLAGS="0x0"
    27  //
    28  // And on Windowws...
    29  //
    30  //     $ SET
    31  //     SESSIONNAME=Console
    32  //     SystemDrive=C:
    33  //     SystemRoot=C:\Windows
    34  //     TEMP=C:\Users\IEUser\AppData\Local\Temp
    35  //     TMP=C:\Users\IEUser\AppData\Local\Temp
    36  //     USERDOMAIN=IE11WIN10
    37  //
    38  func FromExport(body string) *Environment {
    39  	// Create the environment that we'll load values into
    40  	env := &Environment{env: make(map[string]string)}
    41  
    42  	// Remove any white space at the start and the end of the export string
    43  	body = strings.TrimSpace(body)
    44  
    45  	// Normalize \r\n to just \n
    46  	body = strings.Replace(body, "\r\n", "\n", -1)
    47  
    48  	// Split up the export into lines
    49  	lines := strings.Split(body, "\n")
    50  
    51  	// No lines! An empty environment it is@
    52  	if len(lines) == 0 {
    53  		return env
    54  	}
    55  
    56  	// Determine if we're either parsing a Windows or *nix style export
    57  	if posixExportLineRegex.MatchString(lines[0]) {
    58  		var openKeyName string
    59  		var openKeyValue []string
    60  
    61  		for _, line := range lines {
    62  			// Is this line part of a previouly un-closed
    63  			// environment variable?
    64  			if openKeyName != "" {
    65  				// Add the current line to the open variable
    66  				openKeyValue = append(openKeyValue, line)
    67  
    68  				// If it ends with an unescaped quote `"`, then
    69  				// that's the end of the variable!
    70  				if endsWithUnescapedQuoteRegex.MatchString(line) {
    71  					// Join all the lines together
    72  					joinedLines := strings.Join(openKeyValue, "\n")
    73  
    74  					// Remove the `"` at the end
    75  					multiLineValueWithQuoteRemoved := strings.TrimSuffix(joinedLines, `"`)
    76  
    77  					// Set the single line env var
    78  					env.Set(openKeyName, unquoteShell(multiLineValueWithQuoteRemoved))
    79  
    80  					// Set the variables that track an open environment variable
    81  					openKeyName = ""
    82  					openKeyValue = nil
    83  				}
    84  
    85  				// We've finished working on this line, so we
    86  				// can just got the next one
    87  				continue
    88  			}
    89  
    90  			// Trim the `declare -x ` off the start of the line
    91  			line = strings.TrimPrefix(line, "declare -x ")
    92  
    93  			// We now have a line that can either be one of these:
    94  			//
    95  			//     1. `FOO="bar"`
    96  			//     2. `FOO="open quote for new lines`
    97  			//     3. `FOO`
    98  			//
    99  			parts := strings.SplitN(line, "=\"", 2)
   100  			if len(parts) == 2 {
   101  				// If the value ends with an unescaped quote,
   102  				// then we know it's a single line environment
   103  				// variable (see example 1)
   104  				if endsWithUnescapedQuoteRegex.MatchString(parts[1]) {
   105  					// Remove the `"` at the end
   106  					singleLineValueWithQuoteRemoved := strings.TrimSuffix(parts[1], `"`)
   107  
   108  					// Set the single line env var
   109  					env.Set(parts[0], unquoteShell(singleLineValueWithQuoteRemoved))
   110  				} else {
   111  					// We're in an example 2 situation,
   112  					// where we need to keep keep the
   113  					// environment variable open until we
   114  					// encounter a line that ends with an
   115  					// unescaped quote
   116  					openKeyName = parts[0]
   117  					openKeyValue = []string{parts[1]}
   118  				}
   119  			} else {
   120  				// Since there wasn't an `=` sign, we assume
   121  				// it's just an environment variable without a
   122  				// value (see example 3)
   123  				env.Set(parts[0], "")
   124  			}
   125  		}
   126  
   127  		// Return our parsed environment
   128  		return env
   129  	}
   130  
   131  	// Windows exports are easy since they can just be handled by our built-in FromSlice gear
   132  	return FromSlice(lines)
   133  }
   134  
   135  func unquoteShell(value string) string {
   136  	// Turn things like \\n back into \n
   137  	value = strings.Replace(value, `\\`, `\`, -1)
   138  
   139  	// Shells escape $ cause of environment variable interpolation
   140  	value = strings.Replace(value, `\$`, `$`, -1)
   141  
   142  	// They also escape double quotes when showing a value within double
   143  	// quotes, i.e. "this is a \" quote string"
   144  	value = strings.Replace(value, `\"`, `"`, -1)
   145  
   146  	return value
   147  }