"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/npm/lib/utils/tar.js" (16 Apr 2018, 14808 Bytes) of package /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       entry.match(/^.*\.orig$/)
  107     ) {
  108     return false
  109   }
  110 
  111   // in a node_modules folder, we only include bundled dependencies
  112   // also, prevent packages in node_modules from being affected
  113   // by rules set in the containing package, so that
  114   // bundles don't get busted.
  115   // Also, once in a bundle, everything is installed as-is
  116   // To prevent infinite cycles in the case of cyclic deps that are
  117   // linked with npm link, even in a bundle, deps are only bundled
  118   // if they're not already present at a higher level.
  119   if (this.bundleMagic) {
  120     // bubbling up.  stop here and allow anything the bundled pkg allows
  121     if (entry.charAt(0) === '@') {
  122       var firstSlash = entry.indexOf('/')
  123       // continue to list the packages in this scope
  124       if (firstSlash === -1) return true
  125 
  126       // bubbling up.  stop here and allow anything the bundled pkg allows
  127       if (entry.indexOf('/', firstSlash + 1) !== -1) return true
  128     // bubbling up.  stop here and allow anything the bundled pkg allows
  129     } else if (entry.indexOf('/') !== -1) {
  130       return true
  131     }
  132 
  133     // never include the .bin.  It's typically full of platform-specific
  134     // stuff like symlinks and .cmd files anyway.
  135     if (entry === '.bin') return false
  136 
  137     // the package root.
  138     var p = this.parent
  139     // the package before this one.
  140     var pp = p && p.parent
  141 
  142     // if this entry has already been bundled, and is a symlink,
  143     // and it is the *same* symlink as this one, then exclude it.
  144     if (pp && pp.bundleLinks && this.bundleLinks &&
  145         pp.bundleLinks[entry] &&
  146         pp.bundleLinks[entry] === this.bundleLinks[entry]) {
  147       return false
  148     }
  149 
  150     // since it's *not* a symbolic link, if we're *already* in a bundle,
  151     // then we should include everything.
  152     if (pp && pp.package && pp.basename === 'node_modules') {
  153       return true
  154     }
  155 
  156     // only include it at this point if it's a bundleDependency
  157     return this.isBundled(entry)
  158   }
  159   // if (this.bundled) return true
  160 
  161   return Packer.prototype.applyIgnores.call(this, entry, partial, entryObj)
  162 }
  163 
  164 function nameMatch (name) { return function (other) { return name === moduleName(other) } }
  165 
  166 function pack_ (tarball, folder, tree, pkg, cb) {
  167   function InstancePacker (props) {
  168     BundledPacker.call(this, props)
  169   }
  170   inherits(InstancePacker, BundledPacker)
  171   InstancePacker.prototype.isBundled = function (name) {
  172     var bd = this.package && this.package.bundleDependencies
  173     if (!bd) return false
  174 
  175     if (!Array.isArray(bd)) {
  176       throw new Error(packageId(this) + '\'s `bundledDependencies` should ' +
  177                       'be an array')
  178     }
  179     if (!tree) return false
  180 
  181     if (bd.indexOf(name) !== -1) return true
  182     var pkg = tree.children.filter(nameMatch(name))[0]
  183     if (!pkg) return false
  184     var requiredBy = [].concat(pkg.requiredBy)
  185     var seen = {}
  186     while (requiredBy.length) {
  187       var reqPkg = requiredBy.shift()
  188       if (seen[reqPkg.path]) continue
  189       seen[reqPkg.path] = true
  190       if (!reqPkg) continue
  191       if (reqPkg.parent === tree && bd.indexOf(moduleName(reqPkg)) !== -1) {
  192         return true
  193       }
  194       requiredBy = union(requiredBy, reqPkg.requiredBy)
  195     }
  196     return false
  197   }
  198 
  199   new InstancePacker({ path: folder, type: 'Directory', isDirectory: true })
  200     .on('error', function (er) {
  201       if (er) log.error('tar pack', 'Error reading ' + folder)
  202       return cb(er)
  203     })
  204 
  205     // By default, npm includes some proprietary attributes in the
  206     // package tarball.  This is sane, and allowed by the spec.
  207     // However, npm *itself* excludes these from its own package,
  208     // so that it can be more easily bootstrapped using old and
  209     // non-compliant tar implementations.
  210     .pipe(tar.Pack({ noProprietary: !npm.config.get('proprietary-attribs') }))
  211     .on('error', function (er) {
  212       if (er) log.error('tar.pack', 'tar creation error', tarball)
  213       cb(er)
  214     })
  215     .pipe(zlib.Gzip())
  216     .on('error', function (er) {
  217       if (er) log.error('tar.pack', 'gzip error ' + tarball)
  218       cb(er)
  219     })
  220     .pipe(writeStreamAtomic(tarball))
  221     .on('error', function (er) {
  222       if (er) log.error('tar.pack', 'Could not write ' + tarball)
  223       cb(er)
  224     })
  225     .on('close', cb)
  226 }
  227 
  228 function unpack (tarball, unpackTarget, dMode, fMode, uid, gid, cb) {
  229   log.verbose('tar', 'unpack', tarball)
  230   log.verbose('tar', 'unpacking to', unpackTarget)
  231   if (typeof cb !== 'function') {
  232     cb = gid
  233     gid = null
  234   }
  235   if (typeof cb !== 'function') {
  236     cb = uid
  237     uid = null
  238   }
  239   if (typeof cb !== 'function') {
  240     cb = fMode
  241     fMode = npm.modes.file
  242   }
  243   if (typeof cb !== 'function') {
  244     cb = dMode
  245     dMode = npm.modes.exec
  246   }
  247 
  248   uidNumber(uid, gid, function (er, uid, gid) {
  249     if (er) return cb(er)
  250     unpack_(tarball, unpackTarget, dMode, fMode, uid, gid, cb)
  251   })
  252 }
  253 
  254 function unpack_ (tarball, unpackTarget, dMode, fMode, uid, gid, cb) {
  255   rm(unpackTarget, function (er) {
  256     if (er) return cb(er)
  257     // gzip {tarball} --decompress --stdout \
  258     //   | tar -mvxpf - --strip-components=1 -C {unpackTarget}
  259     gunzTarPerm(tarball, unpackTarget,
  260                 dMode, fMode,
  261                 uid, gid,
  262                 function (er, folder) {
  263                   if (er) return cb(er)
  264                   readJson(path.resolve(folder, 'package.json'), cb)
  265                 })
  266   })
  267 }
  268 
  269 function gunzTarPerm (tarball, target, dMode, fMode, uid, gid, cb_) {
  270   if (!dMode) dMode = npm.modes.exec
  271   if (!fMode) fMode = npm.modes.file
  272   log.silly('gunzTarPerm', 'modes', [dMode.toString(8), fMode.toString(8)])
  273 
  274   var cbCalled = false
  275   function cb (er) {
  276     if (cbCalled) return
  277     cbCalled = true
  278     cb_(er, target)
  279   }
  280 
  281   var fst = fs.createReadStream(tarball)
  282 
  283   fst.on('open', function (fd) {
  284     fs.fstat(fd, function (er, st) {
  285       if (er) return fst.emit('error', er)
  286       if (st.size === 0) {
  287         er = new Error('0-byte tarball\n' +
  288                        'Please run `npm cache clean`')
  289         fst.emit('error', er)
  290       }
  291     })
  292   })
  293 
  294   // figure out who we're supposed to be, if we're not pretending
  295   // to be a specific user.
  296   if (npm.config.get('unsafe-perm') && process.platform !== 'win32') {
  297     uid = myUid
  298     gid = myGid
  299   }
  300 
  301   function extractEntry (entry) {
  302     log.silly('gunzTarPerm', 'extractEntry', entry.path)
  303     // never create things that are user-unreadable,
  304     // or dirs that are user-un-listable. Only leads to headaches.
  305     var originalMode = entry.mode = entry.mode || entry.props.mode
  306     entry.mode = entry.mode | (entry.type === 'Directory' ? dMode : fMode)
  307     entry.mode = entry.mode & (~npm.modes.umask)
  308     entry.props.mode = entry.mode
  309     if (originalMode !== entry.mode) {
  310       log.silly('gunzTarPerm', 'modified mode',
  311                 [entry.path, originalMode, entry.mode])
  312     }
  313 
  314     // if there's a specific owner uid/gid that we want, then set that
  315     if (process.platform !== 'win32' &&
  316         typeof uid === 'number' &&
  317         typeof gid === 'number') {
  318       entry.props.uid = entry.uid = uid
  319       entry.props.gid = entry.gid = gid
  320     }
  321   }
  322 
  323   var extractOpts = { type: 'Directory', path: target, strip: 1 }
  324 
  325   if (process.platform !== 'win32' &&
  326       typeof uid === 'number' &&
  327       typeof gid === 'number') {
  328     extractOpts.uid = uid
  329     extractOpts.gid = gid
  330   }
  331 
  332   var sawIgnores = {}
  333   extractOpts.filter = function () {
  334     // symbolic links are not allowed in packages.
  335     if (this.type.match(/^.*Link$/)) {
  336       log.warn('excluding symbolic link',
  337                this.path.substr(target.length + 1) +
  338                ' -> ' + this.linkpath)
  339       return false
  340     }
  341 
  342     // Note: This mirrors logic in the fs read operations that are
  343     // employed during tarball creation, in the fstream-npm module.
  344     // It is duplicated here to handle tarballs that are created
  345     // using other means, such as system tar or git archive.
  346     if (this.type === 'File') {
  347       var base = path.basename(this.path)
  348       if (base === '.npmignore') {
  349         sawIgnores[ this.path ] = true
  350       } else if (base === '.gitignore') {
  351         var npmignore = this.path.replace(/\.gitignore$/, '.npmignore')
  352         if (sawIgnores[npmignore]) {
  353           // Skip this one, already seen.
  354           return false
  355         } else {
  356           // Rename, may be clobbered later.
  357           this.path = npmignore
  358           this._path = npmignore
  359         }
  360       }
  361     }
  362 
  363     return true
  364   }
  365 
  366   fst
  367     .on('error', function (er) {
  368       if (er) log.error('tar.unpack', 'error reading ' + tarball)
  369       cb(er)
  370     })
  371     .on('data', function OD (c) {
  372       // detect what it is.
  373       // Then, depending on that, we'll figure out whether it's
  374       // a single-file module, gzipped tarball, or naked tarball.
  375       // gzipped files all start with 1f8b08
  376       if (c[0] === 0x1F &&
  377           c[1] === 0x8B &&
  378           c[2] === 0x08) {
  379         fst
  380           .pipe(zlib.Unzip())
  381           .on('error', function (er) {
  382             if (er) log.error('tar.unpack', 'unzip error ' + tarball)
  383             cb(er)
  384           })
  385           .pipe(tar.Extract(extractOpts))
  386           .on('entry', extractEntry)
  387           .on('error', function (er) {
  388             if (er) log.error('tar.unpack', 'untar error ' + tarball)
  389             cb(er)
  390           })
  391           .on('close', cb)
  392       } else if (hasTarHeader(c)) {
  393         // naked tar
  394         fst
  395           .pipe(tar.Extract(extractOpts))
  396           .on('entry', extractEntry)
  397           .on('error', function (er) {
  398             if (er) log.error('tar.unpack', 'untar error ' + tarball)
  399             cb(er)
  400           })
  401           .on('close', cb)
  402       } else {
  403         // naked js file
  404         var jsOpts = { path: path.resolve(target, 'index.js') }
  405 
  406         if (process.platform !== 'win32' &&
  407             typeof uid === 'number' &&
  408             typeof gid === 'number') {
  409           jsOpts.uid = uid
  410           jsOpts.gid = gid
  411         }
  412 
  413         fst
  414           .pipe(fstream.Writer(jsOpts))
  415           .on('error', function (er) {
  416             if (er) log.error('tar.unpack', 'copy error ' + tarball)
  417             cb(er)
  418           })
  419           .on('close', function () {
  420             var j = path.resolve(target, 'package.json')
  421             readJson(j, function (er, d) {
  422               if (er) {
  423                 log.error('not a package', tarball)
  424                 return cb(er)
  425               }
  426               writeFileAtomic(j, JSON.stringify(d) + '\n', cb)
  427             })
  428           })
  429       }
  430 
  431       // now un-hook, and re-emit the chunk
  432       fst.removeListener('data', OD)
  433       fst.emit('data', c)
  434     })
  435 }
  436 
  437 function hasTarHeader (c) {
  438   return c[257] === 0x75 && // tar archives have 7573746172 at position
  439          c[258] === 0x73 && // 257 and 003030 or 202000 at position 262
  440          c[259] === 0x74 &&
  441          c[260] === 0x61 &&
  442          c[261] === 0x72 &&
  443 
  444        ((c[262] === 0x00 &&
  445          c[263] === 0x30 &&
  446          c[264] === 0x30) ||
  447 
  448         (c[262] === 0x20 &&
  449          c[263] === 0x20 &&
  450          c[264] === 0x00))
  451 }