"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/npm/lib/utils/tar.js" (8 Mar 2017, 14367 Bytes) of archive /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // commands for packing and unpacking tarballs
    2 // this file is used by lib/cache.js
    3 
    4 var fs = require('graceful-fs')
    5 var path = require('path')
    6 var writeFileAtomic = require('write-file-atomic')
    7 var writeStreamAtomic = require('fs-write-stream-atomic')
    8 var log = require('npmlog')
    9 var uidNumber = require('uid-number')
   10 var readJson = require('read-package-json')
   11 var tar = require('tar')
   12 var zlib = require('zlib')
   13 var fstream = require('fstream')
   14 var Packer = require('fstream-npm')
   15 var iferr = require('iferr')
   16 var inherits = require('inherits')
   17 var npm = require('../npm.js')
   18 var rm = require('./gently-rm.js')
   19 var myUid = process.getuid && process.getuid()
   20 var myGid = process.getgid && process.getgid()
   21 var readPackageTree = require('read-package-tree')
   22 var union = require('lodash.union')
   23 var moduleName = require('./module-name.js')
   24 var packageId = require('./package-id.js')
   25 var pulseTillDone = require('../utils/pulse-till-done.js')
   26 
   27 if (process.env.SUDO_UID && myUid === 0) {
   28   if (!isNaN(process.env.SUDO_UID)) myUid = +process.env.SUDO_UID
   29   if (!isNaN(process.env.SUDO_GID)) myGid = +process.env.SUDO_GID
   30 }
   31 
   32 exports.pack = pack
   33 exports.unpack = unpack
   34 
   35 function pack (tarball, folder, pkg, cb) {
   36   log.verbose('tar pack', [tarball, folder])
   37 
   38   log.verbose('tarball', tarball)
   39   log.verbose('folder', folder)
   40 
   41   readJson(path.join(folder, 'package.json'), function (er, pkg) {
   42     if (er || !pkg.bundleDependencies) {
   43       pack_(tarball, folder, null, pkg, cb)
   44     } else {
   45       // we require this at runtime due to load-order issues, because recursive
   46       // requires fail if you replace the exports object, and we do, not in deps, but
   47       // in a dep of it.
   48       var recalculateMetadata = require('../install/deps.js').recalculateMetadata
   49 
   50       readPackageTree(folder, pulseTillDone('pack:readTree:' + packageId(pkg), iferr(cb, function (tree) {
   51         var recalcGroup = log.newGroup('pack:recalc:' + packageId(pkg))
   52         recalculateMetadata(tree, recalcGroup, iferr(cb, function () {
   53           recalcGroup.finish()
   54           pack_(tarball, folder, tree, pkg, pulseTillDone('pack:' + packageId(pkg), cb))
   55         }))
   56       })))
   57     }
   58   })
   59 }
   60 
   61 function BundledPacker (props) {
   62   Packer.call(this, props)
   63 }
   64 inherits(BundledPacker, Packer)
   65 
   66 BundledPacker.prototype.applyIgnores = function (entry, partial, entryObj) {
   67   if (!entryObj || entryObj.type !== 'Directory') {
   68     // package.json files can never be ignored.
   69     if (entry === 'package.json') return true
   70 
   71     // readme files should never be ignored.
   72     if (entry.match(/^readme(\.[^\.]*)$/i)) return true
   73 
   74     // license files should never be ignored.
   75     if (entry.match(/^(license|licence)(\.[^\.]*)?$/i)) return true
   76 
   77     // copyright notice files should never be ignored.
   78     if (entry.match(/^(notice)(\.[^\.]*)?$/i)) return true
   79 
   80     // changelogs should never be ignored.
   81     if (entry.match(/^(changes|changelog|history)(\.[^\.]*)?$/i)) return true
   82   }
   83 
   84   // special rules.  see below.
   85   if (entry === 'node_modules' && this.packageRoot) return true
   86 
   87   // package.json main file should never be ignored.
   88   var mainFile = this.package && this.package.main
   89   if (mainFile && path.resolve(this.path, entry) === path.resolve(this.path, mainFile)) return true
   90 
   91   // some files are *never* allowed under any circumstances
   92   // (VCS folders, native build cruft, npm cruft, regular cruft)
   93   if (entry === '.git' ||
   94       entry === 'CVS' ||
   95       entry === '.svn' ||
   96       entry === '.hg' ||
   97       entry === '.lock-wscript' ||
   98       entry.match(/^\.wafpickle-[0-9]+$/) ||
   99       (this.parent && this.parent.packageRoot && this.basename === 'build' &&
  100        entry === 'config.gypi') ||
  101       entry === 'npm-debug.log' ||
  102       entry === '.npmrc' ||
  103       entry.match(/^\..*\.swp$/) ||
  104       entry === '.DS_Store' ||
  105       entry.match(/^\._/)
  106     ) {
  107     return false
  108   }
  109 
  110   // in a node_modules folder, we only include bundled dependencies
  111   // also, prevent packages in node_modules from being affected
  112   // by rules set in the containing package, so that
  113   // bundles don't get busted.
  114   // Also, once in a bundle, everything is installed as-is
  115   // To prevent infinite cycles in the case of cyclic deps that are
  116   // linked with npm link, even in a bundle, deps are only bundled
  117   // if they're not already present at a higher level.
  118   if (this.bundleMagic) {
  119     // bubbling up.  stop here and allow anything the bundled pkg allows
  120     if (entry.indexOf('/') !== -1) return true
  121 
  122     // never include the .bin.  It's typically full of platform-specific
  123     // stuff like symlinks and .cmd files anyway.
  124     if (entry === '.bin') return false
  125 
  126     // the package root.
  127     var p = this.parent
  128     // the package before this one.
  129     var pp = p && p.parent
  130 
  131     // if this entry has already been bundled, and is a symlink,
  132     // and it is the *same* symlink as this one, then exclude it.
  133     if (pp && pp.bundleLinks && this.bundleLinks &&
  134         pp.bundleLinks[entry] &&
  135         pp.bundleLinks[entry] === this.bundleLinks[entry]) {
  136       return false
  137     }
  138 
  139     // since it's *not* a symbolic link, if we're *already* in a bundle,
  140     // then we should include everything.
  141     if (pp && pp.package && pp.basename === 'node_modules') {
  142       return true
  143     }
  144 
  145     // only include it at this point if it's a bundleDependency
  146     return this.isBundled(entry)
  147   }
  148   // if (this.bundled) return true
  149 
  150   return Packer.prototype.applyIgnores.call(this, entry, partial, entryObj)
  151 }
  152 
  153 function nameMatch (name) { return function (other) { return name === moduleName(other) } }
  154 
  155 function pack_ (tarball, folder, tree, pkg, cb) {
  156   function InstancePacker (props) {
  157     BundledPacker.call(this, props)
  158   }
  159   inherits(InstancePacker, BundledPacker)
  160   InstancePacker.prototype.isBundled = function (name) {
  161     var bd = this.package && this.package.bundleDependencies
  162     if (!bd) return false
  163 
  164     if (!Array.isArray(bd)) {
  165       throw new Error(packageId(this) + '\'s `bundledDependencies` should ' +
  166                       'be an array')
  167     }
  168     if (!tree) return false
  169 
  170     if (bd.indexOf(name) !== -1) return true
  171     var pkg = tree.children.filter(nameMatch(name))[0]
  172     if (!pkg) return false
  173     var requiredBy = [].concat(pkg.requiredBy)
  174     var seen = {}
  175     while (requiredBy.length) {
  176       var reqPkg = requiredBy.shift()
  177       if (seen[reqPkg.path]) continue
  178       seen[reqPkg.path] = true
  179       if (!reqPkg) continue
  180       if (reqPkg.parent === tree && bd.indexOf(moduleName(reqPkg)) !== -1) {
  181         return true
  182       }
  183       requiredBy = union(requiredBy, reqPkg.requiredBy)
  184     }
  185     return false
  186   }
  187 
  188   new InstancePacker({ path: folder, type: 'Directory', isDirectory: true })
  189     .on('error', function (er) {
  190       if (er) log.error('tar pack', 'Error reading ' + folder)
  191       return cb(er)
  192     })
  193 
  194     // By default, npm includes some proprietary attributes in the
  195     // package tarball.  This is sane, and allowed by the spec.
  196     // However, npm *itself* excludes these from its own package,
  197     // so that it can be more easily bootstrapped using old and
  198     // non-compliant tar implementations.
  199     .pipe(tar.Pack({ noProprietary: !npm.config.get('proprietary-attribs') }))
  200     .on('error', function (er) {
  201       if (er) log.error('tar.pack', 'tar creation error', tarball)
  202       cb(er)
  203     })
  204     .pipe(zlib.Gzip())
  205     .on('error', function (er) {
  206       if (er) log.error('tar.pack', 'gzip error ' + tarball)
  207       cb(er)
  208     })
  209     .pipe(writeStreamAtomic(tarball))
  210     .on('error', function (er) {
  211       if (er) log.error('tar.pack', 'Could not write ' + tarball)
  212       cb(er)
  213     })
  214     .on('close', cb)
  215 }
  216 
  217 function unpack (tarball, unpackTarget, dMode, fMode, uid, gid, cb) {
  218   log.verbose('tar', 'unpack', tarball)
  219   log.verbose('tar', 'unpacking to', unpackTarget)
  220   if (typeof cb !== 'function') {
  221     cb = gid
  222     gid = null
  223   }
  224   if (typeof cb !== 'function') {
  225     cb = uid
  226     uid = null
  227   }
  228   if (typeof cb !== 'function') {
  229     cb = fMode
  230     fMode = npm.modes.file
  231   }
  232   if (typeof cb !== 'function') {
  233     cb = dMode
  234     dMode = npm.modes.exec
  235   }
  236 
  237   uidNumber(uid, gid, function (er, uid, gid) {
  238     if (er) return cb(er)
  239     unpack_(tarball, unpackTarget, dMode, fMode, uid, gid, cb)
  240   })
  241 }
  242 
  243 function unpack_ (tarball, unpackTarget, dMode, fMode, uid, gid, cb) {
  244   rm(unpackTarget, function (er) {
  245     if (er) return cb(er)
  246     // gzip {tarball} --decompress --stdout \
  247     //   | tar -mvxpf - --strip-components=1 -C {unpackTarget}
  248     gunzTarPerm(tarball, unpackTarget,
  249                 dMode, fMode,
  250                 uid, gid,
  251                 function (er, folder) {
  252                   if (er) return cb(er)
  253                   readJson(path.resolve(folder, 'package.json'), cb)
  254                 })
  255   })
  256 }
  257 
  258 function gunzTarPerm (tarball, target, dMode, fMode, uid, gid, cb_) {
  259   if (!dMode) dMode = npm.modes.exec
  260   if (!fMode) fMode = npm.modes.file
  261   log.silly('gunzTarPerm', 'modes', [dMode.toString(8), fMode.toString(8)])
  262 
  263   var cbCalled = false
  264   function cb (er) {
  265     if (cbCalled) return
  266     cbCalled = true
  267     cb_(er, target)
  268   }
  269 
  270   var fst = fs.createReadStream(tarball)
  271 
  272   fst.on('open', function (fd) {
  273     fs.fstat(fd, function (er, st) {
  274       if (er) return fst.emit('error', er)
  275       if (st.size === 0) {
  276         er = new Error('0-byte tarball\n' +
  277                        'Please run `npm cache clean`')
  278         fst.emit('error', er)
  279       }
  280     })
  281   })
  282 
  283   // figure out who we're supposed to be, if we're not pretending
  284   // to be a specific user.
  285   if (npm.config.get('unsafe-perm') && process.platform !== 'win32') {
  286     uid = myUid
  287     gid = myGid
  288   }
  289 
  290   function extractEntry (entry) {
  291     log.silly('gunzTarPerm', 'extractEntry', entry.path)
  292     // never create things that are user-unreadable,
  293     // or dirs that are user-un-listable. Only leads to headaches.
  294     var originalMode = entry.mode = entry.mode || entry.props.mode
  295     entry.mode = entry.mode | (entry.type === 'Directory' ? dMode : fMode)
  296     entry.mode = entry.mode & (~npm.modes.umask)
  297     entry.props.mode = entry.mode
  298     if (originalMode !== entry.mode) {
  299       log.silly('gunzTarPerm', 'modified mode',
  300                 [entry.path, originalMode, entry.mode])
  301     }
  302 
  303     // if there's a specific owner uid/gid that we want, then set that
  304     if (process.platform !== 'win32' &&
  305         typeof uid === 'number' &&
  306         typeof gid === 'number') {
  307       entry.props.uid = entry.uid = uid
  308       entry.props.gid = entry.gid = gid
  309     }
  310   }
  311 
  312   var extractOpts = { type: 'Directory', path: target, strip: 1 }
  313 
  314   if (process.platform !== 'win32' &&
  315       typeof uid === 'number' &&
  316       typeof gid === 'number') {
  317     extractOpts.uid = uid
  318     extractOpts.gid = gid
  319   }
  320 
  321   var sawIgnores = {}
  322   extractOpts.filter = function () {
  323     // symbolic links are not allowed in packages.
  324     if (this.type.match(/^.*Link$/)) {
  325       log.warn('excluding symbolic link',
  326                this.path.substr(target.length + 1) +
  327                ' -> ' + this.linkpath)
  328       return false
  329     }
  330 
  331     // Note: This mirrors logic in the fs read operations that are
  332     // employed during tarball creation, in the fstream-npm module.
  333     // It is duplicated here to handle tarballs that are created
  334     // using other means, such as system tar or git archive.
  335     if (this.type === 'File') {
  336       var base = path.basename(this.path)
  337       if (base === '.npmignore') {
  338         sawIgnores[ this.path ] = true
  339       } else if (base === '.gitignore') {
  340         var npmignore = this.path.replace(/\.gitignore$/, '.npmignore')
  341         if (sawIgnores[npmignore]) {
  342           // Skip this one, already seen.
  343           return false
  344         } else {
  345           // Rename, may be clobbered later.
  346           this.path = npmignore
  347           this._path = npmignore
  348         }
  349       }
  350     }
  351 
  352     return true
  353   }
  354 
  355   fst
  356     .on('error', function (er) {
  357       if (er) log.error('tar.unpack', 'error reading ' + tarball)
  358       cb(er)
  359     })
  360     .on('data', function OD (c) {
  361       // detect what it is.
  362       // Then, depending on that, we'll figure out whether it's
  363       // a single-file module, gzipped tarball, or naked tarball.
  364       // gzipped files all start with 1f8b08
  365       if (c[0] === 0x1F &&
  366           c[1] === 0x8B &&
  367           c[2] === 0x08) {
  368         fst
  369           .pipe(zlib.Unzip())
  370           .on('error', function (er) {
  371             if (er) log.error('tar.unpack', 'unzip error ' + tarball)
  372             cb(er)
  373           })
  374           .pipe(tar.Extract(extractOpts))
  375           .on('entry', extractEntry)
  376           .on('error', function (er) {
  377             if (er) log.error('tar.unpack', 'untar error ' + tarball)
  378             cb(er)
  379           })
  380           .on('close', cb)
  381       } else if (hasTarHeader(c)) {
  382         // naked tar
  383         fst
  384           .pipe(tar.Extract(extractOpts))
  385           .on('entry', extractEntry)
  386           .on('error', function (er) {
  387             if (er) log.error('tar.unpack', 'untar error ' + tarball)
  388             cb(er)
  389           })
  390           .on('close', cb)
  391       } else {
  392         // naked js file
  393         var jsOpts = { path: path.resolve(target, 'index.js') }
  394 
  395         if (process.platform !== 'win32' &&
  396             typeof uid === 'number' &&
  397             typeof gid === 'number') {
  398           jsOpts.uid = uid
  399           jsOpts.gid = gid
  400         }
  401 
  402         fst
  403           .pipe(fstream.Writer(jsOpts))
  404           .on('error', function (er) {
  405             if (er) log.error('tar.unpack', 'copy error ' + tarball)
  406             cb(er)
  407           })
  408           .on('close', function () {
  409             var j = path.resolve(target, 'package.json')
  410             readJson(j, function (er, d) {
  411               if (er) {
  412                 log.error('not a package', tarball)
  413                 return cb(er)
  414               }
  415               writeFileAtomic(j, JSON.stringify(d) + '\n', cb)
  416             })
  417           })
  418       }
  419 
  420       // now un-hook, and re-emit the chunk
  421       fst.removeListener('data', OD)
  422       fst.emit('data', c)
  423     })
  424 }
  425 
  426 function hasTarHeader (c) {
  427   return c[257] === 0x75 && // tar archives have 7573746172 at position
  428          c[258] === 0x73 && // 257 and 003030 or 202000 at position 262
  429          c[259] === 0x74 &&
  430          c[260] === 0x61 &&
  431          c[261] === 0x72 &&
  432 
  433        ((c[262] === 0x00 &&
  434          c[263] === 0x30 &&
  435          c[264] === 0x30) ||
  436 
  437         (c[262] === 0x20 &&
  438          c[263] === 0x20 &&
  439          c[264] === 0x00))
  440 }