Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / phantomjs / install.js
1 // Copyright 2012 The Obvious Corporation.
2
3 /*
4  * This simply fetches the right version of phantom for the current platform.
5  */
6
7 'use strict'
8
9 var requestProgress = require('request-progress')
10 var progress = require('progress')
11 var extractZip = require('extract-zip')
12 var cp = require('child_process')
13 var fs = require('fs-extra')
14 var hasha = require('hasha')
15 var helper = require('./lib/phantomjs')
16 var kew = require('kew')
17 var path = require('path')
18 var request = require('request')
19 var url = require('url')
20 var which = require('which')
21
22 var originalPath = process.env.PATH
23 var DEFAULT_CDN = 'https://github.com/Medium/phantomjs/releases/download/v1.9.19'
24
25 // If the process exits without going through exit(), then we did not complete.
26 var validExit = false
27
28 process.on('exit', function () {
29   if (!validExit) {
30     console.log('Install exited unexpectedly')
31     exit(1)
32   }
33 })
34
35 // NPM adds bin directories to the path, which will cause `which` to find the
36 // bin for this package not the actual phantomjs bin.  Also help out people who
37 // put ./bin on their path
38 process.env.PATH = helper.cleanPath(originalPath)
39
40 var libPath = path.join(__dirname, 'lib')
41 var pkgPath = path.join(libPath, 'phantom')
42 var phantomPath = null
43
44 // If the user manually installed PhantomJS, we want
45 // to use the existing version.
46 //
47 // Do not re-use a manually-installed PhantomJS with
48 // a different version.
49 //
50 // Do not re-use an npm-installed PhantomJS, because
51 // that can lead to weird circular dependencies between
52 // local versions and global versions.
53 // https://github.com/Obvious/phantomjs/issues/85
54 // https://github.com/Medium/phantomjs/pull/184
55 kew.resolve(true)
56   .then(tryPhantomjsInLib)
57   .then(tryPhantomjsOnPath)
58   .then(downloadPhantomjs)
59   .then(extractDownload)
60   .then(function (extractedPath) {
61     return copyIntoPlace(extractedPath, pkgPath)
62   })
63   .then(function () {
64     var location = getTargetPlatform() === 'win32' ?
65         path.join(pkgPath, 'phantomjs.exe') :
66         path.join(pkgPath, 'bin' ,'phantomjs')
67
68     try {
69       // Ensure executable is executable by all users
70       fs.chmodSync(location, '755')
71     } catch (err) {
72       if (err.code == 'ENOENT') {
73         console.error('chmod failed: phantomjs was not successfully copied to', location)
74         exit(1)
75       }
76       throw err
77     }
78
79     var relativeLocation = path.relative(libPath, location)
80     writeLocationFile(relativeLocation)
81
82     console.log('Done. Phantomjs binary available at', location)
83     exit(0)
84   })
85   .fail(function (err) {
86     console.error('Phantom installation failed', err, err.stack)
87     exit(1)
88   })
89
90
91 function writeLocationFile(location) {
92   console.log('Writing location.js file')
93   if (getTargetPlatform() === 'win32') {
94     location = location.replace(/\\/g, '\\\\')
95   }
96
97   var platform = getTargetPlatform()
98   var arch = getTargetArch()
99
100   var contents = 'module.exports.location = "' + location + '"\n'
101
102   if (/^[a-zA-Z0-9]*$/.test(platform) && /^[a-zA-Z0-9]*$/.test(arch)) {
103     contents +=
104         'module.exports.platform = "' + getTargetPlatform() + '"\n' +
105         'module.exports.arch = "' + getTargetArch() + '"\n'
106   }
107
108   fs.writeFileSync(path.join(libPath, 'location.js'), contents)
109 }
110
111 function exit(code) {
112   validExit = true
113   process.env.PATH = originalPath
114   process.exit(code || 0)
115 }
116
117
118 function findSuitableTempDirectory() {
119   var now = Date.now()
120   var candidateTmpDirs = [
121     process.env.TMPDIR || process.env.TEMP || process.env.npm_config_tmp,
122     '/tmp',
123     path.join(process.cwd(), 'tmp')
124   ]
125
126   for (var i = 0; i < candidateTmpDirs.length; i++) {
127     var candidatePath = path.join(candidateTmpDirs[i], 'phantomjs')
128
129     try {
130       fs.mkdirsSync(candidatePath, '0777')
131       // Make double sure we have 0777 permissions; some operating systems
132       // default umask does not allow write by default.
133       fs.chmodSync(candidatePath, '0777')
134       var testFile = path.join(candidatePath, now + '.tmp')
135       fs.writeFileSync(testFile, 'test')
136       fs.unlinkSync(testFile)
137       return candidatePath
138     } catch (e) {
139       console.log(candidatePath, 'is not writable:', e.message)
140     }
141   }
142
143   console.error('Can not find a writable tmp directory, please report issue ' +
144       'on https://github.com/Obvious/phantomjs/issues/59 with as much ' +
145       'information as possible.')
146   exit(1)
147 }
148
149
150 function getRequestOptions() {
151   var strictSSL = !!process.env.npm_config_strict_ssl
152   if (process.version == 'v0.10.34') {
153     console.log('Node v0.10.34 detected, turning off strict ssl due to https://github.com/joyent/node/issues/8894')
154     strictSSL = false
155   }
156
157   var options = {
158     uri: getDownloadUrl(),
159     encoding: null, // Get response as a buffer
160     followRedirect: true, // The default download path redirects to a CDN URL.
161     headers: {},
162     strictSSL: strictSSL
163   }
164
165   var proxyUrl = process.env.npm_config_https_proxy ||
166       process.env.npm_config_http_proxy ||
167       process.env.npm_config_proxy
168   if (proxyUrl) {
169
170     // Print using proxy
171     var proxy = url.parse(proxyUrl)
172     if (proxy.auth) {
173       // Mask password
174       proxy.auth = proxy.auth.replace(/:.*$/, ':******')
175     }
176     console.log('Using proxy ' + url.format(proxy))
177
178     // Enable proxy
179     options.proxy = proxyUrl
180   }
181
182   // Use the user-agent string from the npm config
183   options.headers['User-Agent'] = process.env.npm_config_user_agent
184
185   // Use certificate authority settings from npm
186   var ca = process.env.npm_config_ca
187   if (!ca && process.env.npm_config_cafile) {
188     try {
189       ca = fs.readFileSync(process.env.npm_config_cafile, {encoding: 'utf8'})
190         .split(/\n(?=-----BEGIN CERTIFICATE-----)/g)
191
192       // Comments at the beginning of the file result in the first
193       // item not containing a certificate - in this case the
194       // download will fail
195       if (ca.length > 0 && !/-----BEGIN CERTIFICATE-----/.test(ca[0])) {
196         ca.shift()
197       }
198
199     } catch (e) {
200       console.error('Could not read cafile', process.env.npm_config_cafile, e)
201     }
202   }
203
204   if (ca) {
205     console.log('Using npmconf ca')
206     options.agentOptions = {
207       ca: ca
208     }
209     options.ca = ca
210   }
211
212   return options
213 }
214
215 function handleRequestError(error) {
216   if (error && error.stack && error.stack.indexOf('SELF_SIGNED_CERT_IN_CHAIN') != -1) {
217       console.error('Error making request, SELF_SIGNED_CERT_IN_CHAIN. ' +
218           'Please read https://github.com/Medium/phantomjs#i-am-behind-a-corporate-proxy-that-uses-self-signed-ssl-certificates-to-intercept-encrypted-traffic')
219       exit(1)
220   } else if (error) {
221     console.error('Error making request.\n' + error.stack + '\n\n' +
222         'Please report this full log at https://github.com/Medium/phantomjs')
223     exit(1)
224   } else {
225     console.error('Something unexpected happened, please report this full ' +
226         'log at https://github.com/Medium/phantomjs')
227     exit(1)
228   }
229 }
230
231 function requestBinary(requestOptions, filePath) {
232   var deferred = kew.defer()
233
234   var writePath = filePath + '-download-' + Date.now()
235
236   console.log('Receiving...')
237   var bar = null
238   requestProgress(request(requestOptions, function (error, response, body) {
239     console.log('')
240     if (!error && response.statusCode === 200) {
241       fs.writeFileSync(writePath, body)
242       console.log('Received ' + Math.floor(body.length / 1024) + 'K total.')
243       fs.renameSync(writePath, filePath)
244       deferred.resolve(filePath)
245
246     } else if (response) {
247       console.error('Error requesting archive.\n' +
248           'Status: ' + response.statusCode + '\n' +
249           'Request options: ' + JSON.stringify(requestOptions, null, 2) + '\n' +
250           'Response headers: ' + JSON.stringify(response.headers, null, 2) + '\n' +
251           'Make sure your network and proxy settings are correct.\n\n' +
252           'If you continue to have issues, please report this full log at ' +
253           'https://github.com/Medium/phantomjs')
254       exit(1)
255     } else {
256       handleRequestError(error)
257     }
258   })).on('progress', function (state) {
259     try {
260       if (!bar) {
261         bar = new progress('  [:bar] :percent', {total: state.size.total, width: 40})
262       }
263       bar.curr = state.size.transferred
264       bar.tick()
265     } catch (e) {
266       // It doesn't really matter if the progress bar doesn't update.
267     }
268   })
269   .on('error', handleRequestError)
270
271   return deferred.promise
272 }
273
274
275 function extractDownload(filePath) {
276   var deferred = kew.defer()
277   // extract to a unique directory in case multiple processes are
278   // installing and extracting at once
279   var extractedPath = filePath + '-extract-' + Date.now()
280   var options = {cwd: extractedPath}
281
282   fs.mkdirsSync(extractedPath, '0777')
283   // Make double sure we have 0777 permissions; some operating systems
284   // default umask does not allow write by default.
285   fs.chmodSync(extractedPath, '0777')
286
287   if (filePath.substr(-4) === '.zip') {
288     console.log('Extracting zip contents')
289     extractZip(path.resolve(filePath), {dir: extractedPath}, function(err) {
290       if (err) {
291         console.error('Error extracting zip')
292         deferred.reject(err)
293       } else {
294         deferred.resolve(extractedPath)
295       }
296     })
297
298   } else {
299     console.log('Extracting tar contents (via spawned process)')
300     cp.execFile('tar', ['jxf', path.resolve(filePath)], options, function (err) {
301       if (err) {
302         console.error('Error extracting archive')
303         deferred.reject(err)
304       } else {
305         deferred.resolve(extractedPath)
306       }
307     })
308   }
309   return deferred.promise
310 }
311
312
313 function copyIntoPlace(extractedPath, targetPath) {
314   console.log('Removing', targetPath)
315   return kew.nfcall(fs.remove, targetPath).then(function () {
316     // Look for the extracted directory, so we can rename it.
317     var files = fs.readdirSync(extractedPath)
318     for (var i = 0; i < files.length; i++) {
319       var file = path.join(extractedPath, files[i])
320       if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) != -1) {
321         console.log('Copying extracted folder', file, '->', targetPath)
322         return kew.nfcall(fs.move, file, targetPath)
323       }
324     }
325
326     console.log('Could not find extracted file', files)
327     throw new Error('Could not find extracted file')
328   })
329 }
330
331 function getLocationInLibModuleIfMatching(libPath) {
332   var libModule = require(libPath)
333   if (libModule.location &&
334       getTargetPlatform() == libModule.platform &&
335       getTargetArch() == libModule.arch) {
336     try {
337       var resolvedLocation = path.resolve(path.dirname(libPath), libModule.location)
338       if (fs.statSync(resolvedLocation)) {
339         return resolvedLocation
340       }
341     } catch (e) {
342       // fall through
343     }
344   }
345   return false
346 }
347
348 /**
349  * Check to see if the binary in lib is OK to use. If successful, exit the process.
350  */
351 function tryPhantomjsInLib() {
352   return kew.fcall(function () {
353     var location = getLocationInLibModuleIfMatching('./lib/location.js')
354     if (location) {
355       console.log('PhantomJS is previously installed at', location)
356       exit(0)
357     }
358   }).fail(function () {
359     // silently swallow any errors
360   })
361 }
362
363 /**
364  * Check to see if the binary on PATH is OK to use. If successful, exit the process.
365  */
366 function tryPhantomjsOnPath() {
367   if (getTargetPlatform() != process.platform || getTargetArch() != process.arch) {
368     console.log('Building for target platform ' + getTargetPlatform() + '/' + getTargetArch() +
369                 '. Skipping PATH search')
370     return kew.resolve(false)
371   }
372
373   return kew.nfcall(which, 'phantomjs')
374   .then(function (result) {
375     phantomPath = result
376     console.log('Considering PhantomJS found at', phantomPath)
377
378     // Horrible hack to avoid problems during global install. We check to see if
379     // the file `which` found is our own bin script.
380     if (phantomPath.indexOf(path.join('npm', 'phantomjs')) !== -1) {
381       console.log('Looks like an `npm install -g` on windows; skipping installed version.')
382       return
383     }
384
385     var contents = fs.readFileSync(phantomPath, 'utf8')
386     if (/NPM_INSTALL_MARKER/.test(contents)) {
387       console.log('Looks like an `npm install -g`')
388
389       var globalLocation = getLocationInLibModuleIfMatching(
390           path.resolve(fs.realpathSync(phantomPath), '../../lib/location'))
391       if (globalLocation) {
392         console.log('Linking to global install at', globalLocation)
393         writeLocationFile(globalLocation)
394         exit(0)
395       }
396
397       console.log('Could not link global install, skipping...')
398     } else {
399       return checkPhantomjsVersion(phantomPath).then(function (matches) {
400         if (matches) {
401           writeLocationFile(phantomPath)
402           console.log('PhantomJS is already installed on PATH at', phantomPath)
403           exit(0)
404         }
405       })
406     }
407   }, function () {
408     console.log('PhantomJS not found on PATH')
409   })
410   .fail(function (err) {
411     console.error('Error checking path, continuing', err)
412     return false
413   })
414 }
415
416 /**
417  * @return {?string} Get the download URL for phantomjs.
418  *     May return null if no download url exists.
419  */
420 function getDownloadUrl() {
421   var spec = getDownloadSpec()
422   return spec && spec.url
423 }
424
425 /**
426  * @return {?{url: string, checksum: string}} Get the download URL and expected
427  *     SHA-256 checksum for phantomjs.  May return null if no download url exists.
428  */
429 function getDownloadSpec() {
430   var cdnUrl = process.env.npm_config_phantomjs_cdnurl ||
431       process.env.PHANTOMJS_CDNURL ||
432       DEFAULT_CDN
433   var downloadUrl = cdnUrl + '/phantomjs-' + helper.version + '-'
434   var checksum = ''
435
436   var platform = getTargetPlatform()
437   var arch = getTargetArch()
438   if (platform === 'linux' && arch === 'x64') {
439     downloadUrl += 'linux-x86_64.tar.bz2'
440     checksum = 'a1d9628118e270f26c4ddd1d7f3502a93b48ede334b8585d11c1c3ae7bc7163a'
441   } else if (platform === 'linux' && arch == 'ia32') {
442     downloadUrl += 'linux-i686.tar.bz2'
443     checksum = '4102450bb658157e9aef3e229828fade0aaa0de0663802b31a0edff4b5aedf85'
444   } else if (platform === 'darwin' || platform === 'openbsd' || platform === 'freebsd') {
445     downloadUrl += 'macosx.zip'
446     checksum = '8f15043ae3031815dc5f884ea6ffa053d365491b1bc0dc3a0862d5ff1ac20a48'
447   } else if (platform === 'win32') {
448     downloadUrl += 'windows.zip'
449     checksum = 'da36853ece7d58b6f50813d3e598d8a16bb191b467ac32e1624a239a49de9104'
450   } else {
451     return null
452   }
453   return {url: downloadUrl, checksum: checksum}
454 }
455
456 /**
457  * Download phantomjs, reusing the existing copy on disk if available.
458  * Exits immediately if there is no binary to download.
459  * @return {Promise.<string>} The path to the downloaded file.
460  */
461 function downloadPhantomjs() {
462   var downloadSpec = getDownloadSpec()
463   if (!downloadSpec) {
464     console.error(
465         'Unexpected platform or architecture: ' + getTargetPlatform() + '/' + getTargetArch() + '\n' +
466         'It seems there is no binary available for your platform/architecture\n' +
467         'Try to install PhantomJS globally')
468     exit(1)
469   }
470
471   var downloadUrl = downloadSpec.url
472   var downloadedFile
473
474   return kew.fcall(function () {
475     // Can't use a global version so start a download.
476     var tmpPath = findSuitableTempDirectory()
477     var fileName = downloadUrl.split('/').pop()
478     downloadedFile = path.join(tmpPath, fileName)
479
480     if (fs.existsSync(downloadedFile)) {
481       console.log('Download already available at', downloadedFile)
482       return verifyChecksum(downloadedFile, downloadSpec.checksum)
483     }
484     return false
485   }).then(function (verified) {
486     if (verified) {
487       return downloadedFile
488     }
489
490     // Start the install.
491     console.log('Downloading', downloadUrl)
492     console.log('Saving to', downloadedFile)
493     return requestBinary(getRequestOptions(), downloadedFile)
494   })
495 }
496
497 /**
498  * Check to make sure that the file matches the checksum.
499  * @param {string} fileName
500  * @param {string} checksum
501  * @return {Promise.<boolean>}
502  */
503 function verifyChecksum(fileName, checksum) {
504   return kew.resolve(hasha.fromFile(fileName, {algorithm: 'sha256'})).then(function (hash) {
505     var result = checksum == hash
506     if (result) {
507       console.log('Verified checksum of previously downloaded file')
508     } else {
509       console.log('Checksum did not match')
510     }
511     return result
512   }).fail(function (err) {
513     console.error('Failed to verify checksum: ', err)
514     return false
515   })
516 }
517
518 /**
519  * Check to make sure a given binary is the right version.
520  * @return {kew.Promise.<boolean>}
521  */
522 function checkPhantomjsVersion(phantomPath) {
523   console.log('Found PhantomJS at', phantomPath, '...verifying')
524   return kew.nfcall(cp.execFile, phantomPath, ['--version']).then(function (stdout) {
525     var version = stdout.trim()
526     if (helper.version == version) {
527       return true
528     } else {
529       console.log('PhantomJS detected, but wrong version', stdout.trim(), '@', phantomPath + '.')
530       return false
531     }
532   }).fail(function (err) {
533     console.error('Error verifying phantomjs, continuing', err)
534     return false
535   })
536 }
537
538 /**
539  * @return {string}
540  */
541 function getTargetPlatform() {
542   return process.env.PHANTOMJS_PLATFORM || process.platform
543 }
544
545 /**
546  * @return {string}
547  */
548 function getTargetArch() {
549   return process.env.PHANTOMJS_ARCH || process.arch
550 }