-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Run an executable version of LemMinX
Download a binary lemminx, check its integrity, then run it. Includes: * Setting to specify a binary, `xml.server.binary.path` * Setting to specify args for the binary, `xml.server.binary.args` Defaults to java server in cases such as: * The binary can't be downloaded * The file containing the expected hash of the binary is missing * The hash of the binary doesn't match the expected hash * Binary specified in setting can't be located and the above three fail Signed-off-by: David Thompson <[email protected]>
- Loading branch information
Showing
8 changed files
with
489 additions
and
259 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
root = true | ||
|
||
[*.ts] | ||
[*.js] | ||
indent_style = space | ||
indent_size = 2 | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.vscode | ||
node_modules | ||
src/ | ||
tsconfig.json | ||
webpack.config.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { createHash, Hash } from 'crypto'; | ||
import * as fs from 'fs'; | ||
import * as http from 'http'; | ||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
import { ExtensionContext, window, WorkspaceConfiguration } from "vscode"; | ||
import { Executable } from "vscode-languageclient"; | ||
import { getXMLConfiguration } from "./settings"; | ||
const glob = require('glob'); | ||
|
||
/** | ||
* Returns the executable to launch LemMinX (the XML Language Server) as a binary | ||
* | ||
* @param context the extension context | ||
* @throws if the binary doesn't exist and can't be downloaded, or if the binary is not trusted | ||
* @returns Returns the executable to launch LemMinX (the XML Language Server) as a binary | ||
*/ | ||
export async function prepareBinaryExecutable(context: ExtensionContext): Promise<Executable> { | ||
const binaryOptions: string = getXMLConfiguration().get("server.binary.args"); | ||
let binaryExecutable: Executable; | ||
return getServerBinaryPath() | ||
.then((binaryPath: string) => { | ||
binaryExecutable = { | ||
args: [binaryOptions], | ||
command: binaryPath | ||
} as Executable; | ||
return binaryPath; | ||
}) | ||
.then(checkBinaryHash) | ||
.then((hashOk: boolean) => { | ||
if (hashOk) { | ||
return binaryExecutable; | ||
} else { | ||
return new Promise((resolve, reject) => { | ||
return window.showErrorMessage(`The server binary ${binaryExecutable.command} is not trusted. ` | ||
+ 'Running the file poses a threat to your system\'s security. ' | ||
+ 'Run anyways?', 'Yes', 'No') | ||
.then((val: string) => { | ||
if (val === 'Yes') { | ||
resolve(binaryExecutable); | ||
} | ||
reject("The binary XML language server is not trusted"); | ||
}); | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Returns the path to the LemMinX binary | ||
* | ||
* Downloads it if it is missing | ||
* | ||
* @returns The path to the LemMinX binary | ||
* @throws If the LemMinX binary can't be located or downloaded | ||
*/ | ||
async function getServerBinaryPath(): Promise<string> { | ||
const config: WorkspaceConfiguration = getXMLConfiguration(); | ||
let binaryPath: string = config.get("server.binary.path"); | ||
if (binaryPath) { | ||
if (fs.existsSync(binaryPath)) { | ||
return Promise.resolve(binaryPath); | ||
} | ||
window.showErrorMessage('The specified XML language server binary could not be found. Using the default binary...'); | ||
} | ||
let server_home: string = path.resolve(__dirname, '../server'); | ||
let binaries: Array<string> = glob.sync(`**/lemminx-${os.platform()}*`, { cwd: server_home }); | ||
const JAR_AND_HASH_REJECTOR: RegExp = /(\.jar)|(hash)$/; | ||
binaries = binaries.filter((path) => { return !JAR_AND_HASH_REJECTOR.test(path) }); | ||
if (binaries.length) { | ||
return new Promise<string>((resolve, reject) => { | ||
resolve(path.resolve(server_home, binaries[0])); | ||
}); | ||
} | ||
// Download it, then return the downloaded binary's location | ||
return downloadBinary(); | ||
} | ||
|
||
/** | ||
* Downloads LemMinX binary under the `server` directory and returns the path to the binary as a Promise | ||
* | ||
* @returns The path to the LemMinX binary | ||
* @throws If the LemMinX binary download fails | ||
*/ | ||
async function downloadBinary(): Promise<string> { | ||
window.setStatusBarMessage('Downloading XML language server binary...', 2000); | ||
return new Promise((resolve, reject) => { | ||
const handleResponse = (response: http.IncomingMessage) => { | ||
const statusCode = response.statusCode; | ||
if (statusCode === 303) { | ||
http.get(response.headers.location, handleResponse); | ||
} else if (statusCode === 200) { | ||
// This is probably the problem in Theia TODO: | ||
const serverBinaryPath = path.resolve(__dirname, '../server', getServerBinaryName()); | ||
const serverBinaryFileStream = fs.createWriteStream(serverBinaryPath); | ||
response.pipe(serverBinaryFileStream); | ||
serverBinaryFileStream.on('finish', () => { | ||
serverBinaryFileStream.on('close', () => { | ||
fs.chmodSync(serverBinaryPath, "766"); | ||
resolve(serverBinaryPath); | ||
}); | ||
serverBinaryFileStream.close(); | ||
}); | ||
} else { | ||
reject('Server binary download failed'); | ||
} | ||
} | ||
http.get(`http://localhost:8080/lemminx-redirect/${os.platform()}`, handleResponse) | ||
.on('error', () => { | ||
reject('Server binary download failed'); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Returns true if the hash of the binary matches the expected hash and false otherwise | ||
* | ||
* @param binaryPath the path to the binary to check | ||
* @returns true if the hash of the binary matches the expected hash and false otherwise | ||
*/ | ||
async function checkBinaryHash(binaryPath: string): Promise<boolean> { | ||
const hash: Hash = createHash('sha256'); | ||
return new Promise((resolve, reject) => { | ||
fs.readFile(path.resolve(binaryPath), (err, fileContents) => { | ||
if (err) { | ||
reject(err) | ||
}; | ||
resolve(fileContents); | ||
}); | ||
}) | ||
.then((fileContents: string) => { | ||
hash.update(fileContents); | ||
const hashDigest: string = hash.digest('hex').toLowerCase(); | ||
const expectedHashPath: string = path.resolve(__dirname, '../server', `lemminx-${os.platform}-hash`); | ||
const expectedHash = fs.readFileSync(expectedHashPath).toString('utf-8').toLowerCase().split(' ')[0]; | ||
return hashDigest === expectedHash; | ||
}) | ||
.catch((err: any) => { | ||
return false; | ||
}); | ||
} | ||
|
||
/** | ||
* Returns the name of the LemMinX server binary executable file | ||
* | ||
* @return the name of the LemMinX server binary executable file | ||
*/ | ||
function getServerBinaryName(): string { | ||
return `lemminx-${os.platform()}${os.platform() === 'win32' ? ".exe" : ""}`; | ||
} |
Oops, something went wrong.