UNPKG

10.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const fs = require("fs");
4const os = require("os");
5const path = require("path");
6const debug = require('debug')('netrc-parser');
7function 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 // do not read other machines with same host
29 if (!machines.find(m => m.type === 'machine' && m.host === machine.host))
30 machines.push(machine);
31 if (body.trim().includes(' ')) { // inline machine
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 { // multiline machine
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}
55exports.parse = parse;
56class 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 // do login/password first
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}
211exports.Netrc = Netrc;
212exports.default = new Netrc();
213// this is somewhat complicated but it takes the array of parsed tokens from parse()
214// and it creates ES6 proxy objects to allow them to be easily modified by the consumer of this library
215function proxify(tokens) {
216 const proxifyProps = (t) => new Proxy(t.props, {
217 get(_, key) {
218 if (key === 'host')
219 return t.host;
220 // tslint:disable-next-line strict-type-predicates
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}