1 | var fs = require('fs')
|
2 | , loadWallet = require('../utils/loadWallet')
|
3 | , loadRecipients = require('../utils/loadRecipients')
|
4 | , printHeader = require('../utils/printHeader')
|
5 | , decrypt = require('../lib/decrypt')
|
6 | , Progress = require('progress')
|
7 | , translateHeader = require('../utils/translateHeader')
|
8 | , through = require('through')
|
9 | , pempal = require('pempal')
|
10 | , request = require('micro-request')
|
11 | , assert = require('assert')
|
12 | , fetchGist = require('../utils/fetchGist')
|
13 | , tar = require('tar')
|
14 | , zlib = require('zlib')
|
15 | , path = require('path')
|
16 | , tmpDir = require('os').tmpDir()
|
17 | , crypto = require('crypto')
|
18 | , rimraf = require('rimraf')
|
19 |
|
20 | module.exports = function (inFile, outFile, options) {
|
21 | if (inFile.match(/\.pem$/) || options.gist) {
|
22 | options.armor = true
|
23 | }
|
24 | if (inFile.indexOf('https://gist') === 0) options.gist = true
|
25 | loadWallet(options.parent.wallet, function (err, wallet) {
|
26 | if (err) throw err
|
27 | if (options.gist) {
|
28 | options.armor = true
|
29 | fetchGist(inFile, function (err, str, gist) {
|
30 | if (err) throw err
|
31 | var inStream = through()
|
32 | withInstream(inStream, gist)
|
33 | setImmediate(function () {
|
34 | inStream.end(Buffer(str))
|
35 | })
|
36 | })
|
37 | }
|
38 | else {
|
39 | var inStat = fs.statSync(inFile)
|
40 | var inStream = fs.createReadStream(inFile)
|
41 | withInstream(inStream)
|
42 | }
|
43 | function withInstream (inStream, gist) {
|
44 | var outStream
|
45 | var decryptor, header, outChunks = []
|
46 | function withOutfile () {
|
47 | if (options.gist && !outFile) {
|
48 | outFile = gist.id
|
49 | }
|
50 | if (!outFile) {
|
51 | outFile = inFile.replace(/\.(salty|pem)$/, '')
|
52 | }
|
53 | try {
|
54 | fs.statSync(outFile)
|
55 | if (!options.parent.force) {
|
56 | throw new Error('Refusing to overwrite ' + outFile + '. Use --force to ignore this.')
|
57 | }
|
58 | rimraf.sync(outFile)
|
59 | }
|
60 | catch (err) {
|
61 | if (err && err.code !== 'ENOENT') {
|
62 | throw err
|
63 | }
|
64 | }
|
65 | process.on('uncaughtException', function (err) {
|
66 | console.error('uncaught', err.stack)
|
67 | try {
|
68 | rimraf.sync(outFile)
|
69 | }
|
70 | catch (e) {}
|
71 | throw err
|
72 | })
|
73 | }
|
74 | if (options.armor) {
|
75 | var decodeChunks = [], totalSize
|
76 | var decodeStream = through(function write (buf) {
|
77 | decodeChunks.push(buf)
|
78 | }, function end () {
|
79 | var str = Buffer.concat(decodeChunks).toString('utf8')
|
80 | try {
|
81 | var pem = pempal.decode(str, {tag: 'SALTY MESSAGE'})
|
82 | }
|
83 | catch (e) {
|
84 | throw new Error('invalid PEM')
|
85 | }
|
86 | var tmpStream = through()
|
87 | decryptor = decrypt(tmpStream, wallet, pem.body.length, !options.translate)
|
88 | withDecryptor(decryptor)
|
89 | tmpStream.end(pem.body)
|
90 | })
|
91 | if (options.gist || outFile) {
|
92 | withOutfile()
|
93 | outStream = fs.createWriteStream(outFile, {mode: parseInt('0600', 8)})
|
94 | }
|
95 | else {
|
96 | outStream = through(function write (buf) {
|
97 | outChunks.push(buf)
|
98 | }, function end () {
|
99 | this.on('end', function () {
|
100 | this.emit('finish')
|
101 | })
|
102 | this.queue(null)
|
103 | })
|
104 | }
|
105 | inStream.pipe(decodeStream)
|
106 | }
|
107 | else {
|
108 | withOutfile()
|
109 | outStream = fs.createWriteStream(outFile, {mode: parseInt('0600', 8)})
|
110 | decryptor = decrypt(inStream, wallet, inStat.size, !options.translate)
|
111 | withDecryptor(decryptor)
|
112 | var bar = new Progress(' decrypting [:bar] :percent ETA: :etas', { total: inStat.size, width: 80 })
|
113 | var chunkCounter = 0
|
114 | var tickCounter = 0
|
115 | decryptor.on('data', function (chunk) {
|
116 | tickCounter += chunk.length
|
117 | chunkCounter++
|
118 | if (chunkCounter % 100 === 0) {
|
119 | bar.tick(tickCounter)
|
120 | tickCounter = 0
|
121 | }
|
122 | })
|
123 | }
|
124 | function withDecryptor (decryptor) {
|
125 | decryptor.once('header', function (h) {
|
126 | if (options.sig && !h['signature']) {
|
127 | throw new Error('no signature')
|
128 | }
|
129 | header = options.translate ? translateHeader(h, wallet.recipients) : h
|
130 | if (h['content-encoding'] === 'x-gzip' && h['content-type'] === 'application/x-tar') {
|
131 | var tmpPath = '.' + crypto.randomBytes(16).toString('hex')
|
132 | var extractStream = tar.Extract({path: tmpPath, mode: parseInt('0700', 8)})
|
133 | function onExit () {
|
134 | try {
|
135 | rimraf.sync(tmpPath)
|
136 | rimraf.sync(outFile)
|
137 | }
|
138 | catch (e) {}
|
139 | process.exit(1)
|
140 | }
|
141 | process.once('SIGINT', onExit)
|
142 | process.once('SIGTERM', onExit)
|
143 | var gunzipStream = zlib.createGunzip()
|
144 | extractStream.once('end', function () {
|
145 | rimraf.sync(outFile)
|
146 | fs.renameSync(tmpPath, outFile)
|
147 | printHeader(header)
|
148 | console.log('Restored to', outFile)
|
149 | })
|
150 | gunzipStream.pipe(extractStream)
|
151 | var readStream
|
152 | if (!outFile) {
|
153 | readStream = through()
|
154 | setImmediate(function () {
|
155 | readStream.end(Buffer.concat(outChunks))
|
156 | })
|
157 | withOutfile()
|
158 | }
|
159 | else {
|
160 | var outStat = fs.statSync(outFile)
|
161 | var bar = new Progress(' unpacking [:bar] :percent ETA: :etas', { total: outStat.size, width: 80 })
|
162 | readStream = fs.createReadStream(outFile)
|
163 | var chunkCounter = 0
|
164 | var tickCounter = 0
|
165 | readStream.on('data', function (chunk) {
|
166 | tickCounter += chunk.length
|
167 | chunkCounter++
|
168 | if (chunkCounter % 100 === 0) {
|
169 | bar.tick(tickCounter)
|
170 | tickCounter = 0
|
171 | }
|
172 | })
|
173 | }
|
174 | readStream.pipe(gunzipStream)
|
175 | }
|
176 | else {
|
177 | if (!options.armor) {
|
178 | if (bar) bar.terminate()
|
179 | console.error()
|
180 | printHeader(header)
|
181 | console.log('Decrypted to', outFile)
|
182 | }
|
183 | else {
|
184 | printHeader(header)
|
185 | if (outFile) {
|
186 | var readStream = fs.createReadStream(outFile)
|
187 | readStream.once('end', function () {
|
188 | rimraf.sync(outFile)
|
189 | })
|
190 | readStream.pipe(process.stdout)
|
191 | }
|
192 | else {
|
193 | process.stdout.write(Buffer.concat(outChunks))
|
194 | }
|
195 | }
|
196 | }
|
197 | })
|
198 | outStream.once('finish', function () {
|
199 | if (options.delete && inFile) {
|
200 | try {
|
201 | rimraf.sync(inFile)
|
202 | }
|
203 | catch (e) {}
|
204 | }
|
205 | })
|
206 | decryptor.pipe(outStream)
|
207 | }
|
208 | inStream.resume()
|
209 | }
|
210 | })
|
211 | } |
\ | No newline at end of file |