javaguard.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. const cp = require('child_process')
  2. const EventEmitter = require('events')
  3. const fs = require('fs')
  4. const path = require('path')
  5. const Registry = require('winreg')
  6. const request = require('request')
  7. const PLATFORM_MAP = {
  8. win32: '-windows-x64.tar.gz',
  9. darwin: '-macosx-x64.tar.gz',
  10. linux: '-linux-x64.tar.gz'
  11. }
  12. class JavaGuard extends EventEmitter {
  13. /**
  14. * Validates that a Java binary is at least 64 bit. This makes use of the non-standard
  15. * command line option -XshowSettings:properties. The output of this contains a property,
  16. * sun.arch.data.model = ARCH, in which ARCH is either 32 or 64. This option is supported
  17. * in Java 8 and 9. Since this is a non-standard option. This will resolve to true if
  18. * the function's code throws errors. That would indicate that the option is changed or
  19. * removed.
  20. *
  21. * @param {string} binaryPath Path to the root of the java binary we wish to validate.
  22. *
  23. * @returns {Promise.<boolean>} Resolves to false only if the test is successful and the result
  24. * is less than 64.
  25. */
  26. static _validateBinary(binaryPath){
  27. return new Promise((resolve, reject) => {
  28. const fBp = path.join(binaryPath, 'bin', 'java.exe')
  29. if(fs.existsSync(fBp)){
  30. cp.exec('"' + fBp + '" -XshowSettings:properties', (err, stdout, stderr) => {
  31. try {
  32. // Output is stored in stderr?
  33. const res = stderr
  34. const props = res.split('\n')
  35. for(let i=0; i<props.length; i++){
  36. if(props[i].indexOf('sun.arch.data.model') > -1){
  37. let arch = props[i].split('=')[1].trim()
  38. console.log(props[i].trim() + ' for ' + binaryPath)
  39. resolve(parseInt(arch) >= 64)
  40. }
  41. }
  42. // sun.arch.data.model not found?
  43. // Disregard this test.
  44. resolve(true)
  45. } catch (err){
  46. // Output format might have changed, validation cannot be completed.
  47. // Disregard this test in that case.
  48. resolve(true)
  49. }
  50. })
  51. } else {
  52. resolve(false)
  53. }
  54. })
  55. }
  56. /**
  57. * Checks for the presence of the environment variable JAVA_HOME. If it exits, we will check
  58. * to see if the value points to a path which exists. If the path exits, the path is returned.
  59. *
  60. * @returns {string} The path defined by JAVA_HOME, if it exists. Otherwise null.
  61. */
  62. static _scanJavaHome(){
  63. const jHome = process.env.JAVA_HOME
  64. try {
  65. let res = fs.existsSync(jHome)
  66. return res ? jHome : null
  67. } catch (err) {
  68. // Malformed JAVA_HOME property.
  69. return null
  70. }
  71. }
  72. /**
  73. * Scans the registry for 64-bit Java entries. The paths of each entry are added to
  74. * a set and returned. Currently, only Java 8 (1.8) is supported.
  75. *
  76. * @returns {Promise.<Set.<string>>} A promise which resolves to a set of 64-bit Java root
  77. * paths found in the registry.
  78. */
  79. static _scanRegistry(){
  80. return new Promise((resolve, reject) => {
  81. // Keys for Java v9.0.0 and later:
  82. // 'SOFTWARE\\JavaSoft\\JRE'
  83. // 'SOFTWARE\\JavaSoft\\JDK'
  84. // Forge does not yet support Java 9, therefore we do not.
  85. let cbTracker = 0
  86. let cbAcc = 0
  87. // Keys for Java 1.8 and prior:
  88. const regKeys = [
  89. '\\SOFTWARE\\JavaSoft\\Java Runtime Environment',
  90. '\\SOFTWARE\\JavaSoft\\Java Development Kit'
  91. ]
  92. const candidates = new Set()
  93. for(let i=0; i<regKeys.length; i++){
  94. const key = new Registry({
  95. hive: Registry.HKLM,
  96. key: regKeys[i],
  97. arch: 'x64'
  98. })
  99. key.keyExists((err, exists) => {
  100. if(exists) {
  101. key.keys((err, javaVers) => {
  102. if(err){
  103. console.error(err)
  104. if(i === regKeys.length-1 && cbAcc === cbTracker){
  105. resolve(candidates)
  106. }
  107. } else {
  108. cbTracker += javaVers.length
  109. if(i === regKeys.length-1 && cbTracker === cbAcc){
  110. resolve(candidates)
  111. } else {
  112. for(let j=0; j<javaVers.length; j++){
  113. const javaVer = javaVers[j]
  114. const vKey = javaVer.key.substring(javaVer.key.lastIndexOf('\\')+1)
  115. // Only Java 8 is supported currently.
  116. if(parseFloat(vKey) === 1.8){
  117. javaVer.get('JavaHome', (err, res) => {
  118. const jHome = res.value
  119. if(jHome.indexOf('(x86)') === -1){
  120. candidates.add(jHome)
  121. }
  122. cbAcc++
  123. if(i === regKeys.length-1 && cbAcc === cbTracker){
  124. resolve(candidates)
  125. }
  126. })
  127. } else {
  128. cbAcc++
  129. if(i === regKeys.length-1 && cbAcc === cbTracker){
  130. resolve(candidates)
  131. }
  132. }
  133. }
  134. }
  135. }
  136. })
  137. } else {
  138. if(i === regKeys.length-1 && cbAcc === cbTracker){
  139. resolve(candidates)
  140. }
  141. }
  142. })
  143. }
  144. })
  145. }
  146. /**
  147. * Attempts to find a valid x64 installation of Java on Windows machines.
  148. * Possible paths will be pulled from the registry and the JAVA_HOME environment
  149. * variable. The paths will be sorted with higher versions preceeding lower, and
  150. * JREs preceeding JDKs. The binaries at the sorted paths will then be validated.
  151. * The first validated is returned.
  152. *
  153. * Higher versions > Lower versions
  154. * If versions are equal, JRE > JDK.
  155. *
  156. * @returns {string} The root path of a valid x64 Java installation. If none are
  157. * found, null is returned.
  158. */
  159. static async _win32Validate(){
  160. // Get possible paths from the registry.
  161. const pathSet = await JavaGuard._scanRegistry()
  162. console.log(Array.from(pathSet)) // DEBUGGING
  163. // Validate JAVA_HOME
  164. const jHome = JavaGuard._scanJavaHome()
  165. if(jHome != null && jHome.indexOf('(x86)') === -1){
  166. pathSet.add(jHome)
  167. }
  168. // Convert path set to an array for processing.
  169. let pathArr = Array.from(pathSet)
  170. console.log(pathArr) // DEBUGGING
  171. // Sorts array. Higher version numbers preceed lower. JRE preceeds JDK.
  172. pathArr = pathArr.sort((a, b) => {
  173. // Note that Java 9+ uses semver and that will need to be accounted for in
  174. // the future.
  175. const aVer = parseInt(a.split('_')[1])
  176. const bVer = parseInt(b.split('_')[1])
  177. if(bVer === aVer){
  178. return a.indexOf('jdk') > -1 ? 1 : 0
  179. } else {
  180. return bVer - aVer
  181. }
  182. })
  183. console.log(pathArr) // DEBUGGING
  184. // Validate that the binary is actually x64.
  185. for(let i=0; i<pathArr.length; i++) {
  186. let res = await JavaGuard._validateBinary(pathArr[i])
  187. if(res){
  188. return pathArr[i]
  189. }
  190. }
  191. // No suitable candidates found.
  192. return null;
  193. }
  194. /**
  195. * WIP -> get a valid x64 Java path on macOS.
  196. */
  197. static async _darwinValidate(){
  198. return null
  199. }
  200. /**
  201. * WIP -> get a valid x64 Java path on linux.
  202. */
  203. static async _linuxValidate(){
  204. return null
  205. }
  206. static async validate(){
  207. return await JavaGuard['_' + process.platform + 'Validate']()
  208. }
  209. // WIP -> Downloading Suite
  210. static _latestJREOracle(){
  211. const url = 'http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html'
  212. const regex = /http:\/\/.+?(?=\/java)\/java\/jdk\/([0-9]+u[0-9]+)-(b[0-9]+)\/([a-f0-9]{32})?\/jre-\1/
  213. return new Promise((resolve, reject) => {
  214. request(url, (err, resp, body) => {
  215. if(!err){
  216. const arr = body.match(regex)
  217. const verSplit = arr[1].split('u')
  218. resolve({
  219. uri: arr[0],
  220. version: {
  221. major: verSplit[0],
  222. update: verSplit[1],
  223. build: arr[2]
  224. }
  225. })
  226. } else {
  227. resolve(null)
  228. }
  229. })
  230. })
  231. }
  232. _headOracleJREDlSize(url){
  233. return new Promise((resolve, reject) => {
  234. request.head(url, (err, resp, body) => {
  235. if(err){
  236. reject(err)
  237. } else {
  238. resolve(resp.headers['content-length'])
  239. }
  240. })
  241. })
  242. }
  243. async _downloadOracleJRE(acceptLicense, dir){
  244. if(!acceptLicense){
  245. return
  246. }
  247. // TODO -> Complete this code. See format used in assetguard.js#510
  248. const latestData = await JavaGuard._latestJREOracle()
  249. const combined = latestData.uri + PLATFORM_MAP[process.platform]
  250. const name = combined.substring(combined.lastIndexOf('/')+1)
  251. const fDir = path.join(dir, name)
  252. const opts = {
  253. url: combined,
  254. headers: {
  255. 'Cookie': 'oraclelicense=accept-securebackup-cookie'
  256. }
  257. }
  258. const req = request(opts)
  259. let writeStream = fs.createWriteStream(fDir)
  260. req.pipe(writeStream)
  261. req.resume()
  262. }
  263. _downloadJava(dir){
  264. this._downloadOracleJRE(true, dir)
  265. }
  266. }
  267. async function test(){
  268. //console.log(await JavaGuard.validate())
  269. const g = new JavaGuard()
  270. g._downloadJava('C:\\Users\\Asus\\Desktop\\LauncherElectron\\target\\')
  271. //console.log(JSON.stringify(await _latestJREOracle()))
  272. }
  273. test()
  274. module.exports = {
  275. JavaGuard
  276. }