123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- /**
- * Copyright (c) 2015-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file at
- * https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
- *
- * Modified by Yuxi Evan You
- */
- const fs = require('fs')
- const os = require('os')
- const path = require('path')
- const colors = require('picocolors')
- const childProcess = require('child_process')
- const guessEditor = require('./guess')
- const getArgumentsForPosition = require('./get-args')
- function wrapErrorCallback (cb) {
- return (fileName, errorMessage) => {
- console.log()
- console.log(
- colors.red('Could not open ' + path.basename(fileName) + ' in the editor.')
- )
- if (errorMessage) {
- if (errorMessage[errorMessage.length - 1] !== '.') {
- errorMessage += '.'
- }
- console.log(
- colors.red('The editor process exited with an error: ' + errorMessage)
- )
- }
- console.log()
- if (cb) cb(fileName, errorMessage)
- }
- }
- function isTerminalEditor (editor) {
- switch (editor) {
- case 'vim':
- case 'emacs':
- case 'nano':
- return true
- }
- return false
- }
- const positionRE = /:(\d+)(:(\d+))?$/
- function parseFile (file) {
- const fileName = file.replace(positionRE, '')
- const match = file.match(positionRE)
- const lineNumber = match && match[1]
- const columnNumber = match && match[3]
- return {
- fileName,
- lineNumber,
- columnNumber
- }
- }
- let _childProcess = null
- function launchEditor (file, specifiedEditor, onErrorCallback) {
- const parsed = parseFile(file)
- let { fileName } = parsed
- const { lineNumber, columnNumber } = parsed
- if (!fs.existsSync(fileName)) {
- return
- }
- if (typeof specifiedEditor === 'function') {
- onErrorCallback = specifiedEditor
- specifiedEditor = undefined
- }
- onErrorCallback = wrapErrorCallback(onErrorCallback)
- const [editor, ...args] = guessEditor(specifiedEditor)
- if (!editor) {
- onErrorCallback(fileName, null)
- return
- }
- if (
- process.platform === 'linux' &&
- fileName.startsWith('/mnt/') &&
- /Microsoft/i.test(os.release())
- ) {
- // Assume WSL / "Bash on Ubuntu on Windows" is being used, and
- // that the file exists on the Windows file system.
- // `os.release()` is "4.4.0-43-Microsoft" in the current release
- // build of WSL, see: https://github.com/Microsoft/BashOnWindows/issues/423#issuecomment-221627364
- // When a Windows editor is specified, interop functionality can
- // handle the path translation, but only if a relative path is used.
- fileName = path.relative('', fileName)
- }
- // cmd.exe on Windows is vulnerable to RCE attacks given a file name of the
- // form "C:\Users\myusername\Downloads\& curl 172.21.93.52". Use a safe file
- // name pattern to validate user-provided file names. This doesn't cover the
- // entire range of valid file names but should cover almost all of them in practice.
- // (Backport of
- // https://github.com/facebook/create-react-app/pull/4866
- // and
- // https://github.com/facebook/create-react-app/pull/5431)
- // Allows alphanumeric characters, periods, dashes, slashes, underscores, plus and space.
- const WINDOWS_CMD_SAFE_FILE_NAME_PATTERN = /^([A-Za-z]:[/\\])?[\p{L}0-9/.\-\\_+ ]+$/u
- if (
- process.platform === 'win32' &&
- !WINDOWS_CMD_SAFE_FILE_NAME_PATTERN.test(fileName.trim())
- ) {
- console.log()
- console.log(
- colors.red('Could not open ' + path.basename(fileName) + ' in the editor.')
- )
- console.log()
- console.log(
- 'When running on Windows, file names are checked against a safe file name ' +
- 'pattern to protect against remote code execution attacks. File names ' +
- 'may consist only of alphanumeric characters (all languages), periods, ' +
- 'dashes, slashes, and underscores.'
- );
- console.log()
- return
- }
- if (lineNumber) {
- const extraArgs = getArgumentsForPosition(editor, fileName, lineNumber, columnNumber)
- args.push.apply(args, extraArgs)
- } else {
- args.push(fileName)
- }
- if (_childProcess && isTerminalEditor(editor)) {
- // There's an existing editor process already and it's attached
- // to the terminal, so go kill it. Otherwise two separate editor
- // instances attach to the stdin/stdout which gets confusing.
- _childProcess.kill('SIGKILL')
- }
- if (process.platform === 'win32') {
- // On Windows, launch the editor in a shell because spawn can only
- // launch .exe files.
- _childProcess = childProcess.spawn(
- 'cmd.exe',
- ['/C', editor].concat(args),
- { stdio: 'inherit' }
- )
- } else {
- _childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
- }
- _childProcess.on('exit', function (errorCode) {
- _childProcess = null
- if (errorCode) {
- onErrorCallback(fileName, '(code ' + errorCode + ')')
- }
- })
- _childProcess.on('error', function (error) {
- let { code, message } = error
- if ('ENOENT' === code) {
- message = `${message} ('${editor}' command does not exist in 'PATH')`
- }
- onErrorCallback(fileName, message);
- })
- }
- module.exports = launchEditor
|