source: trip-planner-front/node_modules/tar/lib/write-entry.js@ 571e0df

Last change on this file since 571e0df was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 14.8 KB
Line 
1'use strict'
2const MiniPass = require('minipass')
3const Pax = require('./pax.js')
4const Header = require('./header.js')
5const fs = require('fs')
6const path = require('path')
7const normPath = require('./normalize-windows-path.js')
8const stripSlash = require('./strip-trailing-slashes.js')
9
10const prefixPath = (path, prefix) => {
11 if (!prefix)
12 return normPath(path)
13 path = normPath(path).replace(/^\.(\/|$)/, '')
14 return stripSlash(prefix) + '/' + path
15}
16
17const maxReadSize = 16 * 1024 * 1024
18const PROCESS = Symbol('process')
19const FILE = Symbol('file')
20const DIRECTORY = Symbol('directory')
21const SYMLINK = Symbol('symlink')
22const HARDLINK = Symbol('hardlink')
23const HEADER = Symbol('header')
24const READ = Symbol('read')
25const LSTAT = Symbol('lstat')
26const ONLSTAT = Symbol('onlstat')
27const ONREAD = Symbol('onread')
28const ONREADLINK = Symbol('onreadlink')
29const OPENFILE = Symbol('openfile')
30const ONOPENFILE = Symbol('onopenfile')
31const CLOSE = Symbol('close')
32const MODE = Symbol('mode')
33const AWAITDRAIN = Symbol('awaitDrain')
34const ONDRAIN = Symbol('ondrain')
35const PREFIX = Symbol('prefix')
36const HAD_ERROR = Symbol('hadError')
37const warner = require('./warn-mixin.js')
38const winchars = require('./winchars.js')
39const stripAbsolutePath = require('./strip-absolute-path.js')
40
41const modeFix = require('./mode-fix.js')
42
43const WriteEntry = warner(class WriteEntry extends MiniPass {
44 constructor (p, opt) {
45 opt = opt || {}
46 super(opt)
47 if (typeof p !== 'string')
48 throw new TypeError('path is required')
49 this.path = normPath(p)
50 // suppress atime, ctime, uid, gid, uname, gname
51 this.portable = !!opt.portable
52 // until node has builtin pwnam functions, this'll have to do
53 this.myuid = process.getuid && process.getuid() || 0
54 this.myuser = process.env.USER || ''
55 this.maxReadSize = opt.maxReadSize || maxReadSize
56 this.linkCache = opt.linkCache || new Map()
57 this.statCache = opt.statCache || new Map()
58 this.preservePaths = !!opt.preservePaths
59 this.cwd = normPath(opt.cwd || process.cwd())
60 this.strict = !!opt.strict
61 this.noPax = !!opt.noPax
62 this.noMtime = !!opt.noMtime
63 this.mtime = opt.mtime || null
64 this.prefix = opt.prefix ? normPath(opt.prefix) : null
65
66 this.fd = null
67 this.blockLen = null
68 this.blockRemain = null
69 this.buf = null
70 this.offset = null
71 this.length = null
72 this.pos = null
73 this.remain = null
74
75 if (typeof opt.onwarn === 'function')
76 this.on('warn', opt.onwarn)
77
78 let pathWarn = false
79 if (!this.preservePaths) {
80 const [root, stripped] = stripAbsolutePath(this.path)
81 if (root) {
82 this.path = stripped
83 pathWarn = root
84 }
85 }
86
87 this.win32 = !!opt.win32 || process.platform === 'win32'
88 if (this.win32) {
89 // force the \ to / normalization, since we might not *actually*
90 // be on windows, but want \ to be considered a path separator.
91 this.path = winchars.decode(this.path.replace(/\\/g, '/'))
92 p = p.replace(/\\/g, '/')
93 }
94
95 this.absolute = normPath(opt.absolute || path.resolve(this.cwd, p))
96
97 if (this.path === '')
98 this.path = './'
99
100 if (pathWarn) {
101 this.warn('TAR_ENTRY_INFO', `stripping ${pathWarn} from absolute path`, {
102 entry: this,
103 path: pathWarn + this.path,
104 })
105 }
106
107 if (this.statCache.has(this.absolute))
108 this[ONLSTAT](this.statCache.get(this.absolute))
109 else
110 this[LSTAT]()
111 }
112
113 emit (ev, ...data) {
114 if (ev === 'error')
115 this[HAD_ERROR] = true
116 return super.emit(ev, ...data)
117 }
118
119 [LSTAT] () {
120 fs.lstat(this.absolute, (er, stat) => {
121 if (er)
122 return this.emit('error', er)
123 this[ONLSTAT](stat)
124 })
125 }
126
127 [ONLSTAT] (stat) {
128 this.statCache.set(this.absolute, stat)
129 this.stat = stat
130 if (!stat.isFile())
131 stat.size = 0
132 this.type = getType(stat)
133 this.emit('stat', stat)
134 this[PROCESS]()
135 }
136
137 [PROCESS] () {
138 switch (this.type) {
139 case 'File': return this[FILE]()
140 case 'Directory': return this[DIRECTORY]()
141 case 'SymbolicLink': return this[SYMLINK]()
142 // unsupported types are ignored.
143 default: return this.end()
144 }
145 }
146
147 [MODE] (mode) {
148 return modeFix(mode, this.type === 'Directory', this.portable)
149 }
150
151 [PREFIX] (path) {
152 return prefixPath(path, this.prefix)
153 }
154
155 [HEADER] () {
156 if (this.type === 'Directory' && this.portable)
157 this.noMtime = true
158
159 this.header = new Header({
160 path: this[PREFIX](this.path),
161 // only apply the prefix to hard links.
162 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
163 : this.linkpath,
164 // only the permissions and setuid/setgid/sticky bitflags
165 // not the higher-order bits that specify file type
166 mode: this[MODE](this.stat.mode),
167 uid: this.portable ? null : this.stat.uid,
168 gid: this.portable ? null : this.stat.gid,
169 size: this.stat.size,
170 mtime: this.noMtime ? null : this.mtime || this.stat.mtime,
171 type: this.type,
172 uname: this.portable ? null :
173 this.stat.uid === this.myuid ? this.myuser : '',
174 atime: this.portable ? null : this.stat.atime,
175 ctime: this.portable ? null : this.stat.ctime,
176 })
177
178 if (this.header.encode() && !this.noPax) {
179 super.write(new Pax({
180 atime: this.portable ? null : this.header.atime,
181 ctime: this.portable ? null : this.header.ctime,
182 gid: this.portable ? null : this.header.gid,
183 mtime: this.noMtime ? null : this.mtime || this.header.mtime,
184 path: this[PREFIX](this.path),
185 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
186 : this.linkpath,
187 size: this.header.size,
188 uid: this.portable ? null : this.header.uid,
189 uname: this.portable ? null : this.header.uname,
190 dev: this.portable ? null : this.stat.dev,
191 ino: this.portable ? null : this.stat.ino,
192 nlink: this.portable ? null : this.stat.nlink,
193 }).encode())
194 }
195 super.write(this.header.block)
196 }
197
198 [DIRECTORY] () {
199 if (this.path.substr(-1) !== '/')
200 this.path += '/'
201 this.stat.size = 0
202 this[HEADER]()
203 this.end()
204 }
205
206 [SYMLINK] () {
207 fs.readlink(this.absolute, (er, linkpath) => {
208 if (er)
209 return this.emit('error', er)
210 this[ONREADLINK](linkpath)
211 })
212 }
213
214 [ONREADLINK] (linkpath) {
215 this.linkpath = normPath(linkpath)
216 this[HEADER]()
217 this.end()
218 }
219
220 [HARDLINK] (linkpath) {
221 this.type = 'Link'
222 this.linkpath = normPath(path.relative(this.cwd, linkpath))
223 this.stat.size = 0
224 this[HEADER]()
225 this.end()
226 }
227
228 [FILE] () {
229 if (this.stat.nlink > 1) {
230 const linkKey = this.stat.dev + ':' + this.stat.ino
231 if (this.linkCache.has(linkKey)) {
232 const linkpath = this.linkCache.get(linkKey)
233 if (linkpath.indexOf(this.cwd) === 0)
234 return this[HARDLINK](linkpath)
235 }
236 this.linkCache.set(linkKey, this.absolute)
237 }
238
239 this[HEADER]()
240 if (this.stat.size === 0)
241 return this.end()
242
243 this[OPENFILE]()
244 }
245
246 [OPENFILE] () {
247 fs.open(this.absolute, 'r', (er, fd) => {
248 if (er)
249 return this.emit('error', er)
250 this[ONOPENFILE](fd)
251 })
252 }
253
254 [ONOPENFILE] (fd) {
255 this.fd = fd
256 if (this[HAD_ERROR])
257 return this[CLOSE]()
258
259 this.blockLen = 512 * Math.ceil(this.stat.size / 512)
260 this.blockRemain = this.blockLen
261 const bufLen = Math.min(this.blockLen, this.maxReadSize)
262 this.buf = Buffer.allocUnsafe(bufLen)
263 this.offset = 0
264 this.pos = 0
265 this.remain = this.stat.size
266 this.length = this.buf.length
267 this[READ]()
268 }
269
270 [READ] () {
271 const { fd, buf, offset, length, pos } = this
272 fs.read(fd, buf, offset, length, pos, (er, bytesRead) => {
273 if (er) {
274 // ignoring the error from close(2) is a bad practice, but at
275 // this point we already have an error, don't need another one
276 return this[CLOSE](() => this.emit('error', er))
277 }
278 this[ONREAD](bytesRead)
279 })
280 }
281
282 [CLOSE] (cb) {
283 fs.close(this.fd, cb)
284 }
285
286 [ONREAD] (bytesRead) {
287 if (bytesRead <= 0 && this.remain > 0) {
288 const er = new Error('encountered unexpected EOF')
289 er.path = this.absolute
290 er.syscall = 'read'
291 er.code = 'EOF'
292 return this[CLOSE](() => this.emit('error', er))
293 }
294
295 if (bytesRead > this.remain) {
296 const er = new Error('did not encounter expected EOF')
297 er.path = this.absolute
298 er.syscall = 'read'
299 er.code = 'EOF'
300 return this[CLOSE](() => this.emit('error', er))
301 }
302
303 // null out the rest of the buffer, if we could fit the block padding
304 // at the end of this loop, we've incremented bytesRead and this.remain
305 // to be incremented up to the blockRemain level, as if we had expected
306 // to get a null-padded file, and read it until the end. then we will
307 // decrement both remain and blockRemain by bytesRead, and know that we
308 // reached the expected EOF, without any null buffer to append.
309 if (bytesRead === this.remain) {
310 for (let i = bytesRead; i < this.length && bytesRead < this.blockRemain; i++) {
311 this.buf[i + this.offset] = 0
312 bytesRead++
313 this.remain++
314 }
315 }
316
317 const writeBuf = this.offset === 0 && bytesRead === this.buf.length ?
318 this.buf : this.buf.slice(this.offset, this.offset + bytesRead)
319
320 const flushed = this.write(writeBuf)
321 if (!flushed)
322 this[AWAITDRAIN](() => this[ONDRAIN]())
323 else
324 this[ONDRAIN]()
325 }
326
327 [AWAITDRAIN] (cb) {
328 this.once('drain', cb)
329 }
330
331 write (writeBuf) {
332 if (this.blockRemain < writeBuf.length) {
333 const er = new Error('writing more data than expected')
334 er.path = this.absolute
335 return this.emit('error', er)
336 }
337 this.remain -= writeBuf.length
338 this.blockRemain -= writeBuf.length
339 this.pos += writeBuf.length
340 this.offset += writeBuf.length
341 return super.write(writeBuf)
342 }
343
344 [ONDRAIN] () {
345 if (!this.remain) {
346 if (this.blockRemain)
347 super.write(Buffer.alloc(this.blockRemain))
348 return this[CLOSE](er => er ? this.emit('error', er) : this.end())
349 }
350
351 if (this.offset >= this.length) {
352 // if we only have a smaller bit left to read, alloc a smaller buffer
353 // otherwise, keep it the same length it was before.
354 this.buf = Buffer.allocUnsafe(Math.min(this.blockRemain, this.buf.length))
355 this.offset = 0
356 }
357 this.length = this.buf.length - this.offset
358 this[READ]()
359 }
360})
361
362class WriteEntrySync extends WriteEntry {
363 [LSTAT] () {
364 this[ONLSTAT](fs.lstatSync(this.absolute))
365 }
366
367 [SYMLINK] () {
368 this[ONREADLINK](fs.readlinkSync(this.absolute))
369 }
370
371 [OPENFILE] () {
372 this[ONOPENFILE](fs.openSync(this.absolute, 'r'))
373 }
374
375 [READ] () {
376 let threw = true
377 try {
378 const { fd, buf, offset, length, pos } = this
379 const bytesRead = fs.readSync(fd, buf, offset, length, pos)
380 this[ONREAD](bytesRead)
381 threw = false
382 } finally {
383 // ignoring the error from close(2) is a bad practice, but at
384 // this point we already have an error, don't need another one
385 if (threw) {
386 try {
387 this[CLOSE](() => {})
388 } catch (er) {}
389 }
390 }
391 }
392
393 [AWAITDRAIN] (cb) {
394 cb()
395 }
396
397 [CLOSE] (cb) {
398 fs.closeSync(this.fd)
399 cb()
400 }
401}
402
403const WriteEntryTar = warner(class WriteEntryTar extends MiniPass {
404 constructor (readEntry, opt) {
405 opt = opt || {}
406 super(opt)
407 this.preservePaths = !!opt.preservePaths
408 this.portable = !!opt.portable
409 this.strict = !!opt.strict
410 this.noPax = !!opt.noPax
411 this.noMtime = !!opt.noMtime
412
413 this.readEntry = readEntry
414 this.type = readEntry.type
415 if (this.type === 'Directory' && this.portable)
416 this.noMtime = true
417
418 this.prefix = opt.prefix || null
419
420 this.path = normPath(readEntry.path)
421 this.mode = this[MODE](readEntry.mode)
422 this.uid = this.portable ? null : readEntry.uid
423 this.gid = this.portable ? null : readEntry.gid
424 this.uname = this.portable ? null : readEntry.uname
425 this.gname = this.portable ? null : readEntry.gname
426 this.size = readEntry.size
427 this.mtime = this.noMtime ? null : opt.mtime || readEntry.mtime
428 this.atime = this.portable ? null : readEntry.atime
429 this.ctime = this.portable ? null : readEntry.ctime
430 this.linkpath = normPath(readEntry.linkpath)
431
432 if (typeof opt.onwarn === 'function')
433 this.on('warn', opt.onwarn)
434
435 let pathWarn = false
436 if (!this.preservePaths) {
437 const [root, stripped] = stripAbsolutePath(this.path)
438 if (root) {
439 this.path = stripped
440 pathWarn = root
441 }
442 }
443
444 this.remain = readEntry.size
445 this.blockRemain = readEntry.startBlockSize
446
447 this.header = new Header({
448 path: this[PREFIX](this.path),
449 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
450 : this.linkpath,
451 // only the permissions and setuid/setgid/sticky bitflags
452 // not the higher-order bits that specify file type
453 mode: this.mode,
454 uid: this.portable ? null : this.uid,
455 gid: this.portable ? null : this.gid,
456 size: this.size,
457 mtime: this.noMtime ? null : this.mtime,
458 type: this.type,
459 uname: this.portable ? null : this.uname,
460 atime: this.portable ? null : this.atime,
461 ctime: this.portable ? null : this.ctime,
462 })
463
464 if (pathWarn) {
465 this.warn('TAR_ENTRY_INFO', `stripping ${pathWarn} from absolute path`, {
466 entry: this,
467 path: pathWarn + this.path,
468 })
469 }
470
471 if (this.header.encode() && !this.noPax) {
472 super.write(new Pax({
473 atime: this.portable ? null : this.atime,
474 ctime: this.portable ? null : this.ctime,
475 gid: this.portable ? null : this.gid,
476 mtime: this.noMtime ? null : this.mtime,
477 path: this[PREFIX](this.path),
478 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
479 : this.linkpath,
480 size: this.size,
481 uid: this.portable ? null : this.uid,
482 uname: this.portable ? null : this.uname,
483 dev: this.portable ? null : this.readEntry.dev,
484 ino: this.portable ? null : this.readEntry.ino,
485 nlink: this.portable ? null : this.readEntry.nlink,
486 }).encode())
487 }
488
489 super.write(this.header.block)
490 readEntry.pipe(this)
491 }
492
493 [PREFIX] (path) {
494 return prefixPath(path, this.prefix)
495 }
496
497 [MODE] (mode) {
498 return modeFix(mode, this.type === 'Directory', this.portable)
499 }
500
501 write (data) {
502 const writeLen = data.length
503 if (writeLen > this.blockRemain)
504 throw new Error('writing more to entry than is appropriate')
505 this.blockRemain -= writeLen
506 return super.write(data)
507 }
508
509 end () {
510 if (this.blockRemain)
511 super.write(Buffer.alloc(this.blockRemain))
512 return super.end()
513 }
514})
515
516WriteEntry.Sync = WriteEntrySync
517WriteEntry.Tar = WriteEntryTar
518
519const getType = stat =>
520 stat.isFile() ? 'File'
521 : stat.isDirectory() ? 'Directory'
522 : stat.isSymbolicLink() ? 'SymbolicLink'
523 : 'Unsupported'
524
525module.exports = WriteEntry
Note: See TracBrowser for help on using the repository browser.