no-shot / env
1
/*
2
  BSD 2-Clause "Simplified" License
3
 
4
  Copyright (c) 2015, Scott Motte
5
 
6
  All rights reserved.
7
 
8
  Redistribution and use in source and binary forms, with or without
9
  modification, are permitted provided that the following conditions are met:
10
 
11
  Redistributions of source code must retain the above copyright notice, this
12
  list of conditions and the following disclaimer.
13
 
14
  Redistributions in binary form must reproduce the above copyright notice,
15
  this list of conditions and the following disclaimer in the documentation
16
  and/or other materials provided with the distribution.
17
 
18
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29 1
import { readFileSync } from "fs";
30 1
import { execSync } from "child_process";
31 1
import decrypt from "../decrypt";
32 1
import { logWarning } from "../log";
33 1
import fileExists from "../fileExists";
34
import type { DecryptOptions, Option, ParsedEnvs } from "../index";
35

36
/**
37
 * Parses a string or buffer of Envs into an object.
38
 *
39
 * @param src - contents to be parsed (string | Buffer)
40
 * @param override - allows extracted Envs to be parsed regardless if `process.env` has the properties defined (boolean | string)
41
 * @returns a single object of all { key: value } pairs from `src`
42
 * @example parse(Buffer.from("JUSTICE=league\n"));
43
 */
44 1
export function parse(src: string | Buffer, override?: Option): ParsedEnvs {
45 1
  const { assign } = Object;
46 1
  const { env } = process;
47
  // initialize extracted Envs object
48 1
  const extracted: ParsedEnvs = {};
49

50
  // interprets lines from command line, process.env or .env
51 1
  function interpolate(envValue: string): string {
52
    // find interpolated values with $(KEY) or with $KEY/${KEY}
53
    const matches =
54 1
      envValue.match(/\$\(([^)]+)\)/g) ||
55 1
      envValue.match(/(.?\${?(?:[a-zA-Z0-9_|]+)?}?)/g);
56

57 1
    return !matches
58 1
      ? envValue
59 1
      : matches.reduce((newEnv: string, match: string): string => {
60
          // matches lines against $(command) or $command/${command}
61
          const parts =
62 1
            /(.?)\$\(([^)]+)\)/g.exec(match) ||
63 1
            /(.?)\${?([a-zA-Z0-9_|]+)?}?/g.exec(match);
64

65 1
          const line = parts![0],
66 1
            command = parts![1],
67 1
            stripped = parts![2];
68

69 1
          let value = "",
70 1
            replacePart = line.substring(command.length);
71

72 1
          if (command === "\\") {
73
            // removes escaped characters
74 1
            replacePart = line;
75 1
            value = replacePart.replace("\\$", "$");
76 1
          } else if (line[1] === "(" && line[line.length - 1] === ")") {
77
            // attempts to substitute commands
78 1
            try {
79 1
              value = execSync(stripped, {
80
                stdio: "pipe"
81
              }).toString();
82
            } catch (err) {
83 1
              logWarning(`\x1b[31m${err.message}\x1b[0m`);
84
            }
85
          } else {
86
            // split values for "|": ["key", "default"]
87 1
            const defaultValue = stripped.split("|");
88 1
            const key = defaultValue[0],
89 1
              fallbackValue = defaultValue[1] || "";
90

91
            // interpolate value from process or extracted object or fallback value
92 1
            value = interpolate(env[key] || extracted[key] || fallbackValue);
93
          }
94

95 1
          return newEnv.replace(replacePart, value.trim());
96
        }, envValue);
97
  }
98

99
  // converts Buffers before splitting into lines and processing
100 1
  const keyValues = src.toString().split(/\n|\r|\r\n/);
101

102
  // loops over key value pairs
103 1
  for (let i = 0; i < keyValues.length; i += 1) {
104 1
    const KEYVAL = keyValues[i];
105
    // finds matching "KEY' and 'VAL' in 'KEY=VAL'
106 1
    let keyValueArr = KEYVAL.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/);
107

108 1
    if (keyValueArr) {
109 1
      const key = keyValueArr[1];
110

111
      // prevents the extracted value from overriding a process.env variable
112 1
      if (override || !env[key]) {
113 1
        let value = keyValueArr[2] || "";
114 1
        const end = value.length - 1;
115 1
        const isDoubleQuoted = value[0] === '"' && value[end] === '"';
116 1
        const isSingleQuoted = value[0] === "'" && value[end] === "'";
117

118
        // if single or double quoted, remove quotes
119 1
        if (isSingleQuoted || isDoubleQuoted) {
120 1
          value = value.substring(1, end);
121

122
          // if double quoted, expand newlines
123 1
          if (isDoubleQuoted) value = value.replace(/\\n/g, "\n");
124
        } else {
125 1
          value = interpolate(value);
126
        }
127

128
        // set value to extracted object
129 1
        extracted[key] = value;
130
      }
131

132 1
      continue;
133
    }
134

135
    // finds matching '# extends: path/to/.env'
136 1
    keyValueArr = KEYVAL.match(/(?<=# extends: ).*/g);
137

138
    // checks if there's a matching extension
139 1
    if (keyValueArr) {
140
      // if the file exists, parse it...
141
      // and assign any parsed Envs to extracted
142 1
      if (fileExists(keyValueArr[0]))
143 1
        assign(extracted, parse(readFileSync(keyValueArr[0]), override));
144

145
      // skip to next iteration
146 1
      continue;
147
    }
148

149
    // finds matching '# uses: remoteurl, algorithm, input, encoding, secret, iv'
150 1
    keyValueArr = KEYVAL.match(/(?<=# uses: ).*/g);
151

152
    // checks if there's a match
153 1
    if (keyValueArr) {
154
      // splits string by space
155 1
      const [remoteurl, algorithm, input, encoding, secret, iv] =
156 1
        keyValueArr[0].split(" ");
157

158
      // fetch encrypted string from remoteurl
159 1
      const envs = interpolate(`$(curl -s ${remoteurl})`);
160

161 1
      if (envs && typeof envs === "string") {
162
        // decrypt the encrypted string and convert stringified JSON to Env "KEY=value" pairs
163 1
        const { decryptedEnvs } = decrypt({
164
          algorithm,
165
          encoding,
166
          envs,
167
          input,
168
          iv,
169
          secret
170
        } as DecryptOptions);
171

172
        // assign Envs to extracted
173 1
        assign(extracted, parse(decryptedEnvs));
174
      }
175

176
      // skip to next iteration
177 1
      continue;
178
    }
179
  }
180

181 1
  return extracted;
182
}
183

184 1
export default parse;

Read our documentation on viewing source code .

Loading