1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const fs = require("fs");
|
4 | const os = require("os");
|
5 | const path = require("path");
|
6 | const debug = require('debug')('netrc-parser');
|
7 | function parse(body) {
|
8 | const lines = body.split('\n');
|
9 | let pre = [];
|
10 | let machines = [];
|
11 | while (lines.length) {
|
12 | const line = lines.shift();
|
13 | const match = line.match(/machine\s+((?:[^#\s]+[\s]*)+)(#.*)?$/);
|
14 | if (!match) {
|
15 | pre.push(line);
|
16 | continue;
|
17 | }
|
18 | const [, body, comment] = match;
|
19 | const machine = {
|
20 | type: 'machine',
|
21 | host: body.split(' ')[0],
|
22 | pre: pre.join('\n'),
|
23 | internalWhitespace: '\n ',
|
24 | props: {},
|
25 | comment,
|
26 | };
|
27 | pre = [];
|
28 |
|
29 | if (!machines.find(m => m.type === 'machine' && m.host === machine.host))
|
30 | machines.push(machine);
|
31 | if (body.trim().includes(' ')) {
|
32 | const [host, ...propStrings] = body.split(' ');
|
33 | for (let a = 0; a < propStrings.length; a += 2) {
|
34 | machine.props[propStrings[a]] = { value: propStrings[a + 1] };
|
35 | }
|
36 | machine.host = host;
|
37 | machine.internalWhitespace = ' ';
|
38 | }
|
39 | else {
|
40 | while (lines.length) {
|
41 | const line = lines.shift();
|
42 | const match = line.match(/^(\s+)([\S]+)\s+([\S]+)(\s+#.*)?$/);
|
43 | if (!match) {
|
44 | lines.unshift(line);
|
45 | break;
|
46 | }
|
47 | const [, ws, key, value, comment] = match;
|
48 | machine.props[key] = { value, comment };
|
49 | machine.internalWhitespace = `\n${ws}`;
|
50 | }
|
51 | }
|
52 | }
|
53 | return proxify([...machines, { type: 'other', content: pre.join('\n') }]);
|
54 | }
|
55 | exports.parse = parse;
|
56 | class Netrc {
|
57 | constructor(file) {
|
58 | this.file = file || this.defaultFile;
|
59 | }
|
60 | async load() {
|
61 | try {
|
62 | debug('load', this.file);
|
63 | const decryptFile = async () => {
|
64 | const execa = require('execa');
|
65 | const { code, stdout } = await execa('gpg', this.gpgDecryptArgs, { stdio: [0, null, 2] });
|
66 | if (code !== 0)
|
67 | throw new Error(`gpg exited with code ${code}`);
|
68 | return stdout;
|
69 | };
|
70 | let body = '';
|
71 | if (path.extname(this.file) === '.gpg') {
|
72 | body = await decryptFile();
|
73 | }
|
74 | else {
|
75 | body = await new Promise((resolve, reject) => {
|
76 | fs.readFile(this.file, { encoding: 'utf8' }, (err, data) => {
|
77 | if (err && err.code !== 'ENOENT')
|
78 | reject(err);
|
79 | debug('ENOENT');
|
80 | resolve(data || '');
|
81 | });
|
82 | });
|
83 | }
|
84 | this.machines = parse(body);
|
85 | debug('machines: %o', Object.keys(this.machines));
|
86 | }
|
87 | catch (err) {
|
88 | return this.throw(err);
|
89 | }
|
90 | }
|
91 | loadSync() {
|
92 | try {
|
93 | debug('loadSync', this.file);
|
94 | const decryptFile = () => {
|
95 | const execa = require('execa');
|
96 | const { stdout, status } = execa.sync('gpg', this.gpgDecryptArgs, { stdio: [0, null, 2] });
|
97 | if (status)
|
98 | throw new Error(`gpg exited with code ${status}`);
|
99 | return stdout;
|
100 | };
|
101 | let body = '';
|
102 | if (path.extname(this.file) === '.gpg') {
|
103 | body = decryptFile();
|
104 | }
|
105 | else {
|
106 | try {
|
107 | body = fs.readFileSync(this.file, 'utf8');
|
108 | }
|
109 | catch (err) {
|
110 | if (err.code !== 'ENOENT')
|
111 | throw err;
|
112 | }
|
113 | }
|
114 | this.machines = parse(body);
|
115 | debug('machines: %o', Object.keys(this.machines));
|
116 | }
|
117 | catch (err) {
|
118 | return this.throw(err);
|
119 | }
|
120 | }
|
121 | async save() {
|
122 | debug('save', this.file);
|
123 | let body = this.output;
|
124 | if (this.file.endsWith('.gpg')) {
|
125 | const execa = require('execa');
|
126 | const { stdout, code } = await execa('gpg', this.gpgEncryptArgs, { input: body, stdio: [null, null, 2] });
|
127 | if (code)
|
128 | throw new Error(`gpg exited with code ${code}`);
|
129 | body = stdout;
|
130 | }
|
131 | return new Promise((resolve, reject) => {
|
132 | fs.writeFile(this.file, body, { mode: 0o600 }, err => (err ? reject(err) : resolve()));
|
133 | });
|
134 | }
|
135 | saveSync() {
|
136 | debug('saveSync', this.file);
|
137 | let body = this.output;
|
138 | if (this.file.endsWith('.gpg')) {
|
139 | const execa = require('execa');
|
140 | const { stdout, code } = execa.sync('gpg', this.gpgEncryptArgs, { input: body, stdio: [null, null, 2] });
|
141 | if (code)
|
142 | throw new Error(`gpg exited with code ${status}`);
|
143 | body = stdout;
|
144 | }
|
145 | fs.writeFileSync(this.file, body, { mode: 0o600 });
|
146 | }
|
147 | get output() {
|
148 | let output = [];
|
149 | for (let t of this.machines._tokens) {
|
150 | if (t.type === 'other') {
|
151 | output.push(t.content);
|
152 | continue;
|
153 | }
|
154 | if (t.pre)
|
155 | output.push(t.pre + '\n');
|
156 | output.push(`machine ${t.host}`);
|
157 | const addProps = (t) => {
|
158 | const addProp = (k) => output.push(`${t.internalWhitespace}${k} ${t.props[k].value}${t.props[k].comment || ''}`);
|
159 |
|
160 | if (t.props.login)
|
161 | addProp('login');
|
162 | if (t.props.password)
|
163 | addProp('password');
|
164 | for (let k of Object.keys(t.props).filter(k => !['login', 'password'].includes(k))) {
|
165 | addProp(k);
|
166 | }
|
167 | };
|
168 | const addComment = (t) => t.comment && output.push(' ' + t.comment);
|
169 | if (t.internalWhitespace.includes('\n')) {
|
170 | addComment(t);
|
171 | addProps(t);
|
172 | output.push('\n');
|
173 | }
|
174 | else {
|
175 | addProps(t);
|
176 | addComment(t);
|
177 | output.push('\n');
|
178 | }
|
179 | }
|
180 | return output.join('');
|
181 | }
|
182 | get defaultFile() {
|
183 | const home = (os.platform() === 'win32' &&
|
184 | (process.env.HOME ||
|
185 | (process.env.HOMEDRIVE && process.env.HOMEPATH && path.join(process.env.HOMEDRIVE, process.env.HOMEPATH)) ||
|
186 | process.env.USERPROFILE)) ||
|
187 | os.homedir() ||
|
188 | os.tmpdir();
|
189 | let file = path.join(home, os.platform() === 'win32' ? '_netrc' : '.netrc');
|
190 | return fs.existsSync(file + '.gpg') ? (file += '.gpg') : file;
|
191 | }
|
192 | get gpgDecryptArgs() {
|
193 | const args = ['--batch', '--quiet', '--decrypt', this.file];
|
194 | debug('running gpg with args %o', args);
|
195 | return args;
|
196 | }
|
197 | get gpgEncryptArgs() {
|
198 | const args = ['-a', '--batch', '--default-recipient-self', '-e'];
|
199 | debug('running gpg with args %o', args);
|
200 | return args;
|
201 | }
|
202 | throw(err) {
|
203 | if (err.detail)
|
204 | err.detail += '\n';
|
205 | else
|
206 | err.detail = '';
|
207 | err.detail += `Error occurred during reading netrc file: ${this.file}`;
|
208 | throw err;
|
209 | }
|
210 | }
|
211 | exports.Netrc = Netrc;
|
212 | exports.default = new Netrc();
|
213 |
|
214 |
|
215 | function proxify(tokens) {
|
216 | const proxifyProps = (t) => new Proxy(t.props, {
|
217 | get(_, key) {
|
218 | if (key === 'host')
|
219 | return t.host;
|
220 |
|
221 | if (typeof key !== 'string')
|
222 | return t.props[key];
|
223 | const prop = t.props[key];
|
224 | if (!prop)
|
225 | return;
|
226 | return prop.value;
|
227 | },
|
228 | set(_, key, value) {
|
229 | if (key === 'host') {
|
230 | t.host = value;
|
231 | }
|
232 | else if (!value) {
|
233 | delete t.props[key];
|
234 | }
|
235 | else {
|
236 | t.props[key] = t.props[key] || (t.props[key] = { value: '' });
|
237 | t.props[key].value = value;
|
238 | }
|
239 | return true;
|
240 | },
|
241 | });
|
242 | const machineTokens = tokens.filter((m) => m.type === 'machine');
|
243 | const machines = machineTokens.map(proxifyProps);
|
244 | const getWhitespace = () => {
|
245 | if (!machineTokens.length)
|
246 | return ' ';
|
247 | return machineTokens[machineTokens.length - 1].internalWhitespace;
|
248 | };
|
249 | const obj = {};
|
250 | obj._tokens = tokens;
|
251 | for (let m of machines)
|
252 | obj[m.host] = m;
|
253 | return new Proxy(obj, {
|
254 | set(obj, host, props) {
|
255 | if (!props) {
|
256 | delete obj[host];
|
257 | const idx = tokens.findIndex(m => m.type === 'machine' && m.host === host);
|
258 | if (idx === -1)
|
259 | return true;
|
260 | tokens.splice(idx, 1);
|
261 | return true;
|
262 | }
|
263 | let machine = machines.find(m => m.host === host);
|
264 | if (!machine) {
|
265 | const token = { type: 'machine', host, internalWhitespace: getWhitespace(), props: {} };
|
266 | tokens.push(token);
|
267 | machine = proxifyProps(token);
|
268 | machines.push(machine);
|
269 | obj[host] = machine;
|
270 | }
|
271 | for (let [k, v] of Object.entries(props)) {
|
272 | machine[k] = v;
|
273 | }
|
274 | return true;
|
275 | },
|
276 | deleteProperty(obj, host) {
|
277 | delete obj[host];
|
278 | const idx = tokens.findIndex(m => m.type === 'machine' && m.host === host);
|
279 | if (idx === -1)
|
280 | return true;
|
281 | tokens.splice(idx, 1);
|
282 | return true;
|
283 | },
|
284 | ownKeys() {
|
285 | return machines.map(m => m.host);
|
286 | },
|
287 | });
|
288 | }
|