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 }