| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| lib.rtdep('lib.f', 'lib.fs', |
| 'nassh.CommandInstance', 'nassh.GoogleRelay', |
| 'nassh.PreferenceManager'); |
| |
| /** |
| * The NaCl-ssh-powered terminal command. |
| * |
| * This class defines a command that can be run in an hterm.Terminal instance. |
| * This command creates an instance of the NaCl-ssh plugin and uses it to |
| * communicate with an ssh daemon. |
| * |
| * If you want to use something other than this NaCl plugin to connect to a |
| * remote host (like a shellinaboxd, etc), you'll want to create a brand new |
| * command. |
| * |
| * @param {Object} argv The argument object passed in from the Terminal. |
| */ |
| nassh.CommandInstance = function(argv) { |
| // Command arguments. |
| this.argv_ = argv; |
| |
| // Command environment. |
| this.environment_ = argv.environment || {}; |
| |
| // hterm.Terminal.IO instance. |
| this.io = null; |
| |
| // Relay manager. |
| this.relay_ = null; |
| |
| // Parsed extension manifest. |
| this.manifest_ = null; |
| |
| // The HTML5 persistent FileSystem instance for this extension. |
| this.fileSystem_ = null; |
| |
| // An HTML5 DirectoryEntry for /.ssh/. |
| this.sshDirectoryEntry_ = null; |
| |
| // Root preference manager. |
| this.prefs_ = new nassh.PreferenceManager(); |
| |
| // Counters used to acknowledge writes from the plugin. |
| this.stdoutAcknowledgeCount_ = 0; |
| this.stderrAcknowledgeCount_ = 0; |
| |
| // Prevent us from reporting an exit twice. |
| this.exited_ = false; |
| }; |
| |
| /** |
| * The name of this command used in messages to the user. |
| * |
| * Perhaps this will also be used by the user to invoke this command if we |
| * build a command line shell. |
| */ |
| nassh.CommandInstance.prototype.commandName = 'nassh'; |
| |
| /** |
| * Static run method invoked by the terminal. |
| */ |
| nassh.CommandInstance.run = function(argv) { |
| return new nassh.CommandInstance(argv); |
| }; |
| |
| /** |
| * Start the nassh command. |
| * |
| * Instance run method invoked by the nassh.CommandInstance ctor. |
| */ |
| nassh.CommandInstance.prototype.run = function() { |
| // Useful for console debugging. |
| window.nassh_ = this; |
| |
| this.io = this.argv_.io.push(); |
| |
| // Similar to lib.fs.err, except this logs to the terminal too. |
| var ferr = function(msg) { |
| return function(err) { |
| var ary = Array.apply(null, arguments); |
| console.error(msg + ': ' + ary.join(', ')); |
| |
| this.io.println(nassh.msg('UNEXPECTED_ERROR')); |
| this.io.println(err); |
| }.bind(this); |
| }.bind(this); |
| |
| this.prefs_.readStorage(function() { |
| nassh.loadManifest(onManifestLoaded, ferr('Manifest load failed')); |
| }); |
| |
| var onManifestLoaded = function(manifest) { |
| this.manifest_ = manifest; |
| |
| // Set default window title. |
| this.io.print('\x1b]0;' + this.manifest_.name + ' ' + |
| this.manifest_.version + '\x07'); |
| |
| this.io.println( |
| nassh.msg('WELCOME_VERSION', |
| ['\x1b[1m' + this.manifest_.name + '\x1b[m', |
| '\x1b[1m' + this.manifest_.version + '\x1b[m'])); |
| |
| if (this.manifest_.name.match(/\(tot\)/)) { |
| // If we're a tot version, show how old the hterm deps are. |
| var htermAge = Math.round( |
| (new Date() - |
| new Date(lib.resource.getData('hterm/concat/date'))) / 1000); |
| |
| this.io.println( |
| 'hterm ' + lib.resource.getData('hterm/changelog/version') + ': ' + |
| (htermAge / 60).toFixed(2) + ' minutes ago: ' + |
| (lib.resource.getData('hterm/git/shortstat') || 'no changes')); |
| } |
| |
| this.io.println( |
| nassh.msg('WELCOME_FAQ', ['\x1b[1mhttp://goo.gl/m6Nj8\x1b[m'])); |
| |
| if (hterm.windowType != 'popup') { |
| var osx = window.navigator.userAgent.match(/Mac OS X/); |
| if (!osx) { |
| this.io.println(''); |
| this.io.println( |
| nassh.msg('OPEN_AS_WINDOW_TIP', |
| ['\x1b[1mhttp://goo.gl/OeH3i\x1b[m'])); |
| this.io.println(''); |
| } |
| } |
| |
| nassh.getFileSystem(onFileSystemFound, ferr('FileSystem init failed')); |
| }.bind(this); |
| |
| var onFileSystemFound = function(fileSystem, sshDirectoryEntry) { |
| this.fileSystem_ = fileSystem; |
| this.sshDirectoryEntry_ = sshDirectoryEntry; |
| |
| var argstr = this.argv_.argString; |
| |
| // This item is set before we redirect away to login to a relay server. |
| // If it's set now, it's the first time we're reloading after the redirect. |
| var pendingRelay = window.sessionStorage.getItem('nassh.pendingRelay'); |
| window.sessionStorage.removeItem('nassh.pendingRelay'); |
| |
| if (!argstr || (window.sessionStorage.getItem('nassh.promptOnReload') && |
| !pendingRelay)) { |
| // If promptOnReload is set or we haven't gotten the destination |
| // as an argument then we need to ask the user for the destination. |
| // |
| // The promptOnReload session item allows us to remember that we've |
| // displayed the dialog, so we can re-display it if the user reloads |
| // the page. (Items in sessionStorage are scoped to the tab, kept |
| // between page reloads, and discarded when the tab goes away.) |
| window.sessionStorage.setItem('nassh.promptOnReload', 'yes'); |
| |
| this.promptForDestination_(); |
| } else { |
| if (!this.connectToArgString(argstr)) { |
| this.io.println(nassh.msg('BAD_DESTINATION', [this.argv_.argString])); |
| this.exit(1); |
| } |
| } |
| }.bind(this); |
| }; |
| |
| /** |
| * Export the current list of nassh connections, and any hterm profiles |
| * they reference. |
| * |
| * This is method must be given a completion callback because the hterm |
| * profiles need to be loaded asynchronously. |
| */ |
| nassh.CommandInstance.prototype.exportPreferences = function(onComplete) { |
| var pendingReads = 0; |
| var rv = {}; |
| |
| var onReadStorage = function(terminalProfile, prefs) { |
| rv.hterm[terminalProfile] = prefs.exportAsJson(); |
| if (--pendingReads < 1) |
| onComplete(rv); |
| }; |
| |
| rv.magic = 'nassh-prefs'; |
| rv.version = 1; |
| |
| rv.nassh = this.prefs_.exportAsJson(); |
| rv.hterm = {}; |
| |
| var profileIds = this.prefs_.get('profile-ids'); |
| for (var i = 0; i < profileIds.length; i++) { |
| var nasshProfilePrefs = this.prefs_.getChild('profile-ids', profileIds[i]); |
| var terminalProfile = nasshProfilePrefs.get('terminal-profile'); |
| if (!terminalProfile) |
| terminalProfile = 'default'; |
| |
| if (!(terminalProfile in rv.hterm)) { |
| rv.hterm[terminalProfile] = null; |
| |
| var prefs = new hterm.PreferenceManager(terminalProfile); |
| prefs.readStorage(onReadStorage.bind(null, terminalProfile, prefs)); |
| pendingReads++; |
| } |
| } |
| }; |
| |
| nassh.CommandInstance.prototype.importPreferences = function( |
| json, opt_onComplete) { |
| var pendingReads = 0; |
| |
| var onReadStorage = function(terminalProfile, prefs) { |
| prefs.importFromJson(json.hterm[terminalProfile]); |
| if (--pendingReads < 1 && opt_onComplete) |
| opt_onComplete(rv); |
| }; |
| |
| if (json.magic != 'nassh-prefs') |
| throw new Error('Not a JSON object or bad value for \'magic\'.'); |
| |
| if (json.version != 1) |
| throw new Error('Bad version'); |
| |
| this.prefs_.importFromJson(json.nassh); |
| |
| for (var terminalProfile in json.hterm) { |
| var prefs = new hterm.PreferenceManager(terminalProfile); |
| prefs.readStorage(onReadStorage.bind(null, terminalProfile, prefs)); |
| pendingReads++; |
| } |
| }; |
| |
| /** |
| * Reconnects to host, using the same CommandInstance. |
| * |
| * @param {string} argstr The connection ArgString |
| */ |
| nassh.CommandInstance.prototype.reconnect = function(argstr) { |
| // Terminal reset. |
| this.io.print('\x1b[!p'); |
| |
| this.io = this.argv_.io.push(); |
| |
| this.plugin_.parentNode.removeChild(this.plugin_); |
| this.plugin_ = null; |
| |
| this.stdoutAcknowledgeCount_ = 0; |
| this.stderrAcknowledgeCount_ = 0; |
| |
| this.connectToArgString(argstr); |
| }; |
| |
| /** |
| * Removes a file from the HTML5 filesystem. |
| * |
| * Most likely you want to remove something from the /.ssh/ directory. |
| * |
| * This command is only here to support unsavory JS console hacks for managing |
| * the /.ssh/ directory. |
| * |
| * @param {string} fullPath The full path to the file to remove. |
| */ |
| nassh.CommandInstance.prototype.removeFile = function(fullPath) { |
| lib.fs.removeFile(this.fileSystem_.root, '/.ssh/' + identityName); |
| }; |
| |
| /** |
| * Removes a directory from the HTML5 filesystem. |
| * |
| * Most likely you'll want to remove the entire /.ssh/ directory. |
| * |
| * This command is only here to support unsavory JS console hacks for managing |
| * the /.ssh/ directory. |
| * |
| * @param {string} fullPath The full path to the file to remove. |
| */ |
| nassh.CommandInstance.prototype.removeDirectory = function(fullPath) { |
| this.fileSystem_.root.getDirectory( |
| fullPath, {}, |
| function (f) { |
| f.removeRecursively(lib.fs.log('Removed: ' + fullPath), |
| lib.fs.err('Error removing' + fullPath)); |
| }, |
| lib.fs.log('Error finding: ' + fullPath) |
| ); |
| }; |
| |
| /** |
| * Remove all known hosts. |
| * |
| * This command is only here to support unsavory JS console hacks for managing |
| * the /.ssh/ directory. |
| */ |
| nassh.CommandInstance.prototype.removeAllKnownHosts = function() { |
| this.fileSystem_.root.getFile( |
| '/.ssh/known_hosts', {create: false}, |
| function(fileEntry) { fileEntry.remove(function() {}) }); |
| /* |
| * This isn't necessary, but it makes the user interface a little nicer as |
| * most people don't realize that "undefined" is what you get from a void |
| * javascript function. Example console output: |
| * > term_.command.removeAllKnownHosts() |
| * true |
| */ |
| return true; |
| }; |
| |
| /** |
| * Remove a known host by index. |
| * |
| * This command is only here to support unsavory JS console hacks for managing |
| * the /.ssh/ directory. |
| * |
| * @param {integer} index One-based index of the known host entry to remove. |
| */ |
| nassh.CommandInstance.prototype.removeKnownHostByIndex = function(index) { |
| var onError = lib.fs.log('Error accessing /.ssh/known_hosts'); |
| var self = this; |
| |
| lib.fs.readFile( |
| self.fileSystem_.root, '/.ssh/known_hosts', |
| function(contents) { |
| var ary = contents.split('\n'); |
| ary.splice(index - 1, 1); |
| lib.fs.overwriteFile(self.fileSystem_.root, '/.ssh/known_hosts', |
| ary.join('\n'), |
| lib.fs.log('done'), |
| onError); |
| }, onError); |
| }; |
| |
| nassh.CommandInstance.prototype.promptForDestination_ = function(opt_default) { |
| var connectDialog = this.io.createFrame( |
| lib.f.getURL('/html/nassh_connect_dialog.html'), null); |
| |
| connectDialog.onMessage = function(event) { |
| event.data.argv.unshift(connectDialog); |
| this.dispatchMessage_('connect-dialog', this.onConnectDialog_, event.data); |
| }.bind(this); |
| |
| connectDialog.show(); |
| }; |
| |
| nassh.CommandInstance.prototype.connectToArgString = function(argstr) { |
| var ary = argstr.match(/^profile-id:([a-z0-9]+)(\?.*)?/i); |
| var rv; |
| if (ary) { |
| rv = this.connectToProfile(ary[1], ary[2]); |
| } else { |
| rv = this.connectToDestination(argstr); |
| } |
| |
| return rv; |
| }; |
| |
| /** |
| * Initiate a connection to a remote host given a profile id. |
| */ |
| nassh.CommandInstance.prototype.connectToProfile = function( |
| profileID, querystr) { |
| |
| var onReadStorage = function() { |
| // TODO(rginda): Soft fail on unknown profileID. |
| var prefs = this.prefs_.getProfile(profileID); |
| |
| // We have to set the url here rather than in connectToArgString, because |
| // some callers will come directly to connectToProfile. |
| document.location.hash = 'profile-id:' + profileID; |
| |
| document.title = prefs.get('description') + ' - ' + |
| this.manifest_.name + ' ' + this.manifest_.version; |
| |
| this.connectTo({ |
| username: prefs.get('username'), |
| hostname: prefs.get('hostname'), |
| port: prefs.get('port'), |
| relayHost: prefs.get('relay-host'), |
| relayPort: prefs.get('relay-port'), |
| relayOptions: prefs.get('relay-options'), |
| identity: prefs.get('identity'), |
| argstr: prefs.get('argstr'), |
| terminalProfile: prefs.get('terminal-profile') |
| }); |
| }.bind(this); |
| |
| // Re-read prefs from storage in case they were just changed in the connect |
| // dialog. |
| this.prefs_.readStorage(onReadStorage); |
| |
| return true; |
| }; |
| |
| /** |
| * Initiate a connection to a remote host given a destination string. |
| * |
| * @param {string} destination A string of the form username@host[:port]. |
| * @return {boolean} True if we were able to parse the destination string, |
| * false otherwise. |
| */ |
| nassh.CommandInstance.prototype.connectToDestination = function(destination) { |
| if (destination == 'crosh') { |
| document.location = 'crosh.html' |
| return true; |
| } |
| |
| var ary = destination.match( |
| /^([^@]+)@([^:@]+)(?::(\d+))?(?:@([^:]+)(?::(\d+))?)?$/); |
| |
| if (!ary) |
| return false; |
| |
| // We have to set the url here rather than in connectToArgString, because |
| // some callers may come directly to connectToDestination. |
| document.location.hash = destination; |
| |
| return this.connectTo({ |
| username: ary[1], |
| hostname: ary[2], |
| port: ary[3], |
| relayHost: ary[4], |
| relayPort: ary[5] |
| }); |
| }; |
| |
| /** |
| * Initiate a connection to a remote host. |
| * |
| * @param {string} username The username to provide. |
| * @param {string} hostname The hostname or IP address to connect to. |
| * @param {string|integer} opt_port The optional port number to connect to. |
| * @return {boolean} False if there was some trouble with the parameters, true |
| * otherwise. |
| */ |
| nassh.CommandInstance.prototype.connectTo = function(params) { |
| if (!(params.username && params.hostname)) |
| return false; |
| |
| if (params.relayHost) { |
| this.relay_ = new nassh.GoogleRelay(this.io, |
| params.relayHost, |
| params.relayPort, |
| params.relayOptions); |
| this.io.println(nassh.msg( |
| 'INITIALIZING_RELAY', |
| [this.relay_.proxyHost + ':' + this.relay_.proxyPort])); |
| |
| if (!this.relay_.init()) { |
| // A false return value means we have to redirect to complete |
| // initialization. Bail out of the connect for now. We'll resume it |
| // when the relay is done with its redirect. |
| |
| // If we're going to have to redirect for the relay then we should make |
| // sure not to re-prompt for the destination when we return. |
| sessionStorage.setItem('nassh.pendingRelay', 'yes'); |
| this.relay_.redirect(); |
| return true; |
| } |
| } |
| |
| this.io.setTerminalProfile(params.terminalProfile || 'default'); |
| |
| // TODO(rginda): The "port" parameter was removed from the CONNECTING message |
| // on May 9, 2012, however the translations haven't caught up yet. We should |
| // remove the port parameter here once they do. |
| this.io.println(nassh.msg('CONNECTING', |
| [params.username + '@' + params.hostname, |
| (params.port || '??')])); |
| this.io.onVTKeystroke = this.sendString_.bind(this); |
| this.io.sendString = this.sendString_.bind(this); |
| this.io.onTerminalResize = this.onTerminalResize_.bind(this); |
| |
| var argv = {}; |
| argv.terminalWidth = this.io.terminal_.screenSize.width; |
| argv.terminalHeight = this.io.terminal_.screenSize.height; |
| argv.useJsSocket = !!this.relay_; |
| argv.environment = this.environment_; |
| argv.writeWindow = 8 * 1024; |
| |
| argv.arguments = ['-C']; // enable compression |
| |
| // Disable IP address check for connection through proxy. |
| if (argv.useJsSocket) |
| argv.arguments.push("-o CheckHostIP=no"); |
| |
| var commandArgs; |
| if (params.argstr) { |
| var ary = params.argstr.match(/^(.*?)(?:(?:^|\s+)(?:--\s+(.*)))?$/); |
| if (ary) { |
| console.log(ary); |
| if (ary[1]) |
| argv.arguments = argv.arguments.concat(ary[1].split(/\s+/)); |
| commandArgs = ary[2]; |
| } |
| } |
| |
| if (params.identity) |
| argv.arguments.push('-i/.ssh/' + params.identity); |
| if (params.port) |
| argv.arguments.push('-p' + params.port); |
| |
| argv.arguments.push(params.username + '@' + params.hostname); |
| if (commandArgs) |
| argv.arguments.push(commandArgs); |
| |
| var self = this; |
| this.initPlugin_(function() { |
| window.onbeforeunload = self.onBeforeUnload_.bind(self); |
| self.sendToPlugin_('startSession', [argv]); |
| }); |
| |
| document.querySelector('#terminal').focus(); |
| |
| return true; |
| }; |
| |
| /** |
| * Dispatch a "message" to one of a collection of message handlers. |
| */ |
| nassh.CommandInstance.prototype.dispatchMessage_ = function( |
| desc, handlers, msg) { |
| if (msg.name in handlers) { |
| handlers[msg.name].apply(this, msg.argv); |
| } else { |
| console.log('Unknown "' + desc + '" message: ' + msg.name); |
| } |
| }; |
| |
| nassh.CommandInstance.prototype.initPlugin_ = function(onComplete) { |
| var self = this; |
| function onPluginLoaded() { |
| self.io.println(nassh.msg('PLUGIN_LOADING_COMPLETE')); |
| onComplete(); |
| }; |
| |
| this.io.print(nassh.msg('PLUGIN_LOADING')); |
| |
| this.plugin_ = window.document.createElement('embed'); |
| this.plugin_.style.cssText = |
| ('position: absolute;' + |
| 'top: -99px' + |
| 'width: 0;' + |
| 'height: 0;'); |
| |
| var ary = navigator.userAgent.match(/Chrom(e|ium)\/(\d\d)\./); |
| var chromeVersion = parseInt(ary[2]) || 24; |
| var isARM = (/arm/i).test(navigator.platform); |
| |
| var pluginURL; |
| if (isARM && chromeVersion == 23) { |
| // TODO(rginda): Remove (ARM && Chrome 23) plugin once Chrome 23 is history. |
| pluginURL = '../plugin/arm_23/ssh_client.nmf'; |
| } else { |
| pluginURL = '../plugin/pnacl/ssh_client.nmf'; |
| } |
| |
| this.plugin_.setAttribute('src', pluginURL); |
| this.plugin_.setAttribute('type', 'application/x-nacl'); |
| this.plugin_.addEventListener('load', onPluginLoaded); |
| this.plugin_.addEventListener('message', this.onPluginMessage_.bind(this)); |
| this.plugin_.addEventListener('crash', function (ev) { |
| console.log('plugin crashed'); |
| self.exit(-1); |
| }); |
| |
| document.body.insertBefore(this.plugin_, document.body.firstChild); |
| }; |
| |
| /** |
| * Send a message to the nassh plugin. |
| * |
| * @param {string} name The name of the message to send. |
| * @param {Array} arguments The message arguments. |
| */ |
| nassh.CommandInstance.prototype.sendToPlugin_ = function(name, args) { |
| var str = JSON.stringify({name: name, arguments: args}); |
| |
| this.plugin_.postMessage(str); |
| }; |
| |
| /** |
| * Send a string to the remote host. |
| * |
| * @param {string} string The string to send. |
| */ |
| nassh.CommandInstance.prototype.sendString_ = function(string) { |
| this.sendToPlugin_('onRead', [0, btoa(string)]); |
| }; |
| |
| /** |
| * Notify plugin about new terminal size. |
| * |
| * @param {string|integer} terminal width. |
| * @param {string|integer} terminal height. |
| */ |
| nassh.CommandInstance.prototype.onTerminalResize_ = function(width, height) { |
| this.sendToPlugin_('onResize', [Number(width), Number(height)]); |
| }; |
| |
| /** |
| * Exit the nassh command. |
| */ |
| nassh.CommandInstance.prototype.exit = function(code) { |
| window.onbeforeunload = null; |
| |
| this.io.println(nassh.msg('DISCONNECT_MESSAGE', [code])); |
| this.io.println(nassh.msg('RECONNECT_MESSAGE')); |
| this.io.onVTKeystroke = function(string) { |
| var ch = string.toLowerCase(); |
| if (ch == 'r' || ch == ' ' || ch == '\x0d' /* enter */) |
| this.reconnect(document.location.hash.substr(1)); |
| |
| if (ch == 'c' || ch == '\x12' /* ctrl-r */) { |
| document.location.hash = ''; |
| document.location.reload(); |
| return; |
| } |
| |
| if (ch == 'e' || ch == 'x' || ch == '\x1b' /* ESC */ || |
| ch == '\x17' /* C-w */) { |
| if (this.exited_) |
| return; |
| |
| this.exited_ = true; |
| this.io.pop(); |
| if (this.argv_.onExit) |
| this.argv_.onExit(code); |
| } |
| }.bind(this); |
| }; |
| |
| nassh.CommandInstance.prototype.onBeforeUnload_ = function(e) { |
| if (hterm.windowType == 'popup') |
| return; |
| |
| var msg = nassh.msg('BEFORE_UNLOAD'); |
| e.returnValue = msg; |
| return msg; |
| }; |
| |
| /** |
| * Called when the plugin sends us a message. |
| * |
| * Plugin messages are JSON strings rather than arbitrary JS values. They |
| * also use "arguments" instead of "argv". This function translates the |
| * plugin message into something dispatchMessage_ can digest. |
| */ |
| nassh.CommandInstance.prototype.onPluginMessage_ = function(e) { |
| var msg = JSON.parse(e.data); |
| msg.argv = msg.arguments; |
| this.dispatchMessage_('plugin', this.onPlugin_, msg); |
| }; |
| |
| /** |
| * Connect dialog message handlers. |
| */ |
| nassh.CommandInstance.prototype.onConnectDialog_ = {}; |
| |
| /** |
| * Sent from the dialog when the user chooses a profile. |
| */ |
| nassh.CommandInstance.prototype.onConnectDialog_.connectToProfile = function( |
| dialogFrame, profileID) { |
| dialogFrame.close(); |
| |
| if (!this.connectToProfile(profileID)) |
| this.promptForDestination_(); |
| }; |
| |
| /** |
| * Plugin message handlers. |
| */ |
| nassh.CommandInstance.prototype.onPlugin_ = {}; |
| |
| /** |
| * Log a message from the plugin. |
| */ |
| nassh.CommandInstance.prototype.onPlugin_.printLog = function(str) { |
| console.log('plugin log: ' + str); |
| }; |
| |
| /** |
| * Plugin has exited. |
| */ |
| nassh.CommandInstance.prototype.onPlugin_.exit = function(code) { |
| console.log('plugin exit: ' + code); |
| this.sendToPlugin_('onExitAcknowledge', []); |
| this.exit(code); |
| }; |
| |
| /** |
| * Plugin wants to open a file. |
| * |
| * The plugin leans on JS to provide a persistent filesystem, which we do via |
| * the HTML5 Filesystem API. |
| * |
| * In the future, the plugin may handle its own files. |
| */ |
| nassh.CommandInstance.prototype.onPlugin_.openFile = function(fd, path, mode) { |
| var self = this; |
| function onOpen(success) { |
| self.sendToPlugin_('onOpenFile', [fd, success]); |
| } |
| |
| if (path == '/dev/random') { |
| var streamClass = nassh.Stream.Random; |
| var stream = nassh.Stream.openStream(streamClass, fd, path, onOpen); |
| stream.onClose = function(reason) { |
| self.sendToPlugin_('onClose', [fd, reason]); |
| }; |
| } else { |
| self.sendToPlugin_('onOpenFile', [fd, false]); |
| } |
| }; |
| |
| nassh.CommandInstance.prototype.onPlugin_.openSocket = function( |
| fd, host, port) { |
| if (!this.relay_) { |
| this.sendToPlugin_('onOpenSocket', [fd, false]); |
| return; |
| } |
| |
| var self = this; |
| var stream = this.relay_.openSocket( |
| fd, host, port, |
| function onOpen(success) { |
| self.sendToPlugin_('onOpenSocket', [fd, success]); |
| }); |
| |
| stream.onDataAvailable = function(data) { |
| self.sendToPlugin_('onRead', [fd, data]); |
| }; |
| |
| stream.onClose = function(reason) { |
| console.log('close: ' + fd); |
| self.sendToPlugin_('onClose', [fd, reason]); |
| }; |
| }; |
| |
| /** |
| * Plugin wants to write some data to a file descriptor. |
| * |
| * This is used to write to HTML5 Filesystem files. |
| */ |
| nassh.CommandInstance.prototype.onPlugin_.write = function(fd, data) { |
| var self = this; |
| |
| if (fd == 1 || fd == 2) { |
| var string = atob(data); |
| var ackCount = (fd == 1 ? |
| this.stdoutAcknowledgeCount_ += string.length : |
| this.stderrAcknowledgeCount_ += string.length); |
| this.io.writeUTF8(string); |
| |
| setTimeout(function() { |
| self.sendToPlugin_('onWriteAcknowledge', [fd, ackCount]); |
| }, 0); |
| return; |
| } |
| |
| var stream = nassh.Stream.getStreamByFd(fd); |
| if (!stream) { |
| console.warn('Attempt to write to unknown fd: ' + fd); |
| return; |
| } |
| |
| stream.asyncWrite(data, function(writeCount) { |
| self.sendToPlugin_('onWriteAcknowledge', [fd, writeCount]); |
| }, 100); |
| }; |
| |
| /** |
| * Plugin wants to read from a fd. |
| */ |
| nassh.CommandInstance.prototype.onPlugin_.read = function(fd, size) { |
| var self = this; |
| var stream = nassh.Stream.getStreamByFd(fd); |
| |
| if (!stream) { |
| if (fd) |
| console.warn('Attempt to read from unknown fd: ' + fd); |
| return; |
| } |
| |
| stream.asyncRead(size, function(b64bytes) { |
| self.sendToPlugin_('onRead', [fd, b64bytes]); |
| }); |
| }; |
| |
| /** |
| * Plugin wants to close a file descriptor. |
| */ |
| nassh.CommandInstance.prototype.onPlugin_.close = function(fd) { |
| var self = this; |
| var stream = nassh.Stream.getStreamByFd(fd); |
| if (!stream) { |
| console.warn('Attempt to close unknown fd: ' + fd); |
| return; |
| } |
| |
| stream.close(); |
| }; |