/*
 * Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
 * Browser Exploitation Framework (BeEF) - http://beefproject.com
 * See the file 'doc/COPYING' for copying permission
 */

/**
 * @Author :        venkatakrishnan ganesh
 * @file   :        wterm.jquery.js
 * @url    :        http://wterminal.appspot.com
 * @desc   :
 *
 * Allows Emulation of Terminal on the browser.
 * Completely Extendible. 
 * Command History.
 * Commandline Editing. 
 *
 * Modified by antisnatchor (also to prevent XSS, see line 270)
 * */

( function( $ ) {

   
  var VERSION = '0.0.4';

  /**
  *
  * @function : get_defaults
  * @returns  : Object
  * @desc     : Returns Global Defaults
  *
  * */
  var get_defaults = function() {

    return {

      // PS1 : The Primary Prompt
      PS1                : 'BeEF-bind-$',

      // TERMINAL_CLASS  
      // Will be applied to the primary terminal container
      TERMINAL_CLASS     : 'wterm_terminal',

      // PROMPT_CLASS 
      // Will Applied to prompt container
      PROMPT_CLASS       : 'wterm_prompt',

      // THEME_CLASS_PREFIX
      // All Theme Classes will be prefixed by this string
      THEME_CLASS_PREFIX : 'wterm_theme',

      // DEFAULT_THEME
      // The theme that is applied by default
      DEFAULT_THEME      : '_green_on_black',

      // HIGHLIGHT_CLASS
      // The Class that is applied to highlighted text
      HIGHLIGHT_CLASS    : 'wterm_highlight',

      // KEYWORD_CLASS
      // The Class that is applied to keywords
      KEYWORD_CLASS      : 'wterm_keyword',

      // CONTENT_CLASS 
      // The Class that is applied to content section 
      KEYWORD_CLASS      : 'wterm_content',

      // WIDTH | HIGHT
      // Explicitly set width and height of the terminal
      // container. This may also be done in TERMINAL_CLASS
      WIDTH              : '90%',
      HEIGHT             : '90%', 

      // WELCOME_MESSAGE
      // Message to be shown when the terminal is first 
      // published
      WELCOME_MESSAGE    : 'Welcome to Wterm version-' + VERSION ,

      // NOT_FOUND
      // Message to be published if the command is not found
      // Note: "CMD" will be replaced with the actual command
      NOT_FOUND          : '<div> CMD: Command Not Found </div>',

      // AUTOCOMPLETE
      // Is Autocomplete feature Enabled
      // Please see the manual on how AUTOCOMPLETE is implemented
      AUTOCOMPLETE      : true,

      // HISTORY
      // Is Command History Enabled
      HISTORY           : true,

      // HISTORY
      // No of entries to be stored in HISTORY
      HISTORY_ENTRIES  : 100,


      // AJAX_METHOD
      // The HTTP Method that must be used for Ajax Requests
      AJAX_METHOD      : 'GET',


      // AJAX_PARAMETER
      // The GET/POST parameter that should be used to make requests
      AJAX_PARAM      : 'tokens',

      // ERROR_PREFIX
      // Prefix For Error Messages
      ERROR_PREFIX    : 'An Error Occured: '

    };
  };

 
  /**
  * @property : dispatch
  * @accessor : $.register_command ( See Below )
  * @private
  * @desc     : 
  *
  * dispatch table stores command name and action
  * to be taken when user enters a command. See
  * Manual for more details on how to implement 
  * your own commands
  *
  **/
  var dispatch = { 
  };


  /**
  *
  * @method : wterm
  * @public
  * @desc   : Sets up the terminal on the JQ object that 
  * represents a ( or a group ) of HTML NODE (s)
  *
  **/
  $.fn.wterm = function( options ) {


    // Merge defaults with options
    var settings = get_defaults();
    $.extend( true, settings, options );

    // JQ Plugin surprised??
    return this.each( function() {

      
      var element  = $( this );
      var history  = [ ];
      var hcurrent = null;
      
      // Set up some markup in the element
      // required for terminal emulation
      element.addClass( settings.TERMINAL_CLASS ).addClass( settings.THEME_CLASS_PREFIX + settings.DEFAULT_THEME );
      if( settings.WIDTH && settings.HEIGHT ) element.css( { width: settings.WIDTH, height: settings.HEIGHT } )
      element.html( '' ).append( '<div>' + settings.WELCOME_MESSAGE + '</div>' );

      element.append( '<div class="' + settings.CONTENT_CLASS + '"></div>' );
      element.append( '<div><span class="' + settings.PROMPT_CLASS + '">' + settings.PS1 + '&nbsp;</span>' +
                      '<form> <input type="text" ></form></div>' ); 


      // Representing prompt, form, input and content section 
      // in the terminal
      var _prompt    = element.find( 'div:last span:last' );
      var input_form = element.find( 'div:last form' );
      var input      = element.find( 'div:last form input' );
      var content    = element.find( '.' + settings.CONTENT_CLASS );

      // Custom Dispatcher
      var cdispatch  = null;

      // Temprary storage for autocomplete configuration
      var ac_save    = null;

      // Temporary store for current prompt
      var cprompt    = null;
      
      // Curson always needs to be on the prompt
      input.focus();
      element.click( function() { input.focus(); } );


      /**
      * @method   : hide
      * @private  :
      * @desc     : Hides the prompt
      **/
      var hide = function() {
        _prompt.hide();
      };

      /**
      * @method   : show 
      * @private  :
      * @desc     : Shows the prompt
      **/
      var show = function() {
        _prompt.show();
        input.focus();
      };
   
      /**
      * @method   : update_content
      * @private  :
      * @desc     : Updates the content section
      * @args     : current_prompt, command, data
      **/
      var update_content = function( p, cmd, data ) {
        content.append( '<div><span>' + p + ' ' + cmd + '</span><div>' + ( ( data ) ? data : '' ) + '</div></div>' );
      }; 

      /**
      * @method   : clear_content
      * @private  :
      * @desc     : Updates the content section
      * @args     : current_prompt, command, data
      **/
      var clear_content = function() {
        content.html( '' );
      };

      // Add the command to the dispatch
      dispatch.clear = clear_content;



      /**
      * @method   : set_prompt 
      * @private  :
      * @desc     : Set the current prompt
      * @args     : string 
      **/
      set_prompt = function( p ) {
        if( p && p.length ) element.find( '.' + settings.PROMPT_CLASS).html( p + '&nbsp;' );
      };

 

      /**
      *
      * @method   : Anonymous
      * @private  :
      * @event_handler
      *
      **/
      input_form.submit( function( e ) {
        e.preventDefault();
        e.stopPropagation();

        var value = input.attr( 'value' );

        if( settings.HISTORY ) {
          if( history.length > settings.HISTORY_ENTRIES ) history.shift();
          history.push( value );
        }
        
        // Reset The Input
        input.attr( 'value', '' );
        var tokens = value.split( /\s+/ );
        var key    = tokens[0];
        
        hide(); 

        var get_current_prompt = function() {
          return ( cprompt ) ? cprompt : settings.PS1;
        }

        var _dispatch = function( key, tokens ) {

          if( typeof key === 'function' ) {
              data = key( tokens );
              /*
               * antisnatchor: preventing XSS
               */
              if( data ) { update_content( get_current_prompt(), $jEncoder.encoder.encodeForJavascript(value), data)}
          } else if( typeof key === 'string' ) {
            var to_send = { };
            to_send[ settings.AJAX_PARAM ] = tokens.join( ' ' );

            var on_complete = function( data, text_status ) {
              update_content( get_current_prompt(), value, data )
            };
           
            $[ settings.AJAX_METHOD.toLowerCase() ](  key, to_send, on_complete );
          }
        };

        if( key == '' ) {
          update_content( get_current_prompt() , '' )
        } else if( cdispatch && key == 'exit' ) {
           
           // Recover old configuration and Dispatch exit hook
           settings.AUTOCOMPLETE = ( ac_save ) ? ac_save : false ;

           // Todo: test what happens when exit hook is not defined
           if( cdispatch.EXIT_HOOK ) {
             _dispatch( cdispatch.EXIT_HOOK, tokens );
           } else {
             _dispatch( function() { return '<b></b>' }, tokens );
           }
       
          // Clear temporary values
          cdispatch = null;  
          cprompt   = null;

          // Reset the prompt
          set_prompt( settings.PS1 );

        } else if( cdispatch ) {

          // Dispatch to the custom dispatcher
          _dispatch( cdispatch.DISPATCH, tokens ); 

        } else if( dispatch[ key ] ) {
          if( typeof dispatch[ key ] === 'object' ) {
            cdispatch = dispatch[ key ];
            cprompt   = cdispatch.PS1 || key;
            set_prompt( cprompt );

            ac_save = settings.AUTOCOMPLETE;
            settings.AUTOCOMPLETE = false;

            // Todo:See what happens if start hook is not defined
            if( cdispatch.START_HOOK ) {
              _dispatch( cdispatch.START_HOOK, tokens );
            } else {
              // A stupid Hack
              _dispatch( function() { return '<b></b>' }, tokens );
            }
          } else {
            _dispatch( dispatch[ key ], tokens );
          } 
        } else {
          update_content( settings.PS1, value, settings.NOT_FOUND.replace( 'CMD', tokens[0] ));
        }

        show();


      } );


      /**
      *
      * @method   : Anonymous
      * @private  :
      * @event_handler
      *
      **/
      input.keydown( function( e ) {
        var keycode = e.keyCode;
        switch( keycode ) {

          case 9:

            e.preventDefault();

            if( settings.AUTOCOMPLETE ) {
              var commands      = [ ];   
              var current_value = input.attr( 'value' );
              // Command Completion
              if( current_value.match( /^[^\s]{0,}$/ ) ) {
                for( i in dispatch ) {
                  if( current_value == '' ) {
                    commands.push( i );
                  } else if( i.indexOf( current_value ) == 0 ) {
                    commands.push( i );
                  }
                }
               
                if( commands.length > 1 ) { 
                  update_content( settings.PS1, current_value, commands.join( '<br>' ) );
                } else if( commands.length == 1 ) {
                  input.attr( 'value', commands.pop() + ' ' );  
                }
              }
            }

          break;

          // History Up
          case 38:
            e.preventDefault();
            if( settings.HISTORY ) {
              hcurrent  = ( hcurrent === null )? history.length - 1 : ( hcurrent == 0 ) ? history.length - 1 : hcurrent - 1;
              input.attr( 'value', history[ hcurrent ] );
            }
          break;

          // History Down
          case 40:
            e.preventDefault();
            if( settings.HISTORY ) {
              if( hcurrent === null || hcurrent == (history.length - 1 ) ) break;
              hcurrent++;
              input.attr( 'value', history[ hcurrent ] );
            }
          break;

          default:
          break;
        }
      });
    });

  };


  $.register_command = function( command, dispatch_method ) {
    try {
      if( typeof dispatch_method === 'function' || typeof dispatch_method === 'string' || typeof dispatch_method === 'object' ) {
        dispatch[ command ] = dispatch_method;
      } else {
        throw 'Dispatch needs to be a method';
      }
    } catch ( e ) {
      // Error Handling here
    }
  };

})( jQuery );


var $jwterm = jQuery.noConflict();