<commit_before>'''
FeatherDuster - A wizard-like interface to cryptanalib
Author: <NAME>

FeatherDuster is a tool for brushing away magical crypto fairy dust.
'''

import sys
import readline
import completer
from ishell.console import Console
from ishell.command import Command

import feathermodules

feathermodules.samples = []
feathermodules.results = False
feathermodules.selected_attack_name = ''
feathermodules.current_options = {}

from feathermodules.stream import *
from feathermodules.block import *
from feathermodules.classical import *
from feathermodules.auxiliary import *
from feathermodules.custom import *
from feathermodules.pubkey import *

import cryptanalib as ca

# import
class ImportMultiFileCommand(Command):
   def run(self, line):
      ishellCompleter = readline.get_completer()
      readline.set_completer_delims(' \t\n;')
      readline.parse_and_bind("tab: complete")
      readline.set_completer(completer.complete)

      sample_file = raw_input('Please enter the filename you want to open: ')
      try:
         sample_fh = open(sample_file,'r')
         feathermodules.samples.extend([sample.strip() for sample in sample_fh.readlines()])
         sample_fh.close()
         feathermodules.samples = filter(lambda x: x != '' and x != None, feathermodules.samples)
      except:
         print 'Something went wrong. Sorry! Please try again.'
      finally:
         readline.set_completer(ishellCompleter)

class ImportSingleFileCommand(Command):
   def run(self, line):
      ishellCompleter = readline.get_completer()
      readline.set_completer_delims(' \t\n;')
      readline.parse_and_bind("tab: complete")
      readline.set_completer(completer.complete)

      sample_file = raw_input('Please enter the filename you want to open: ')
      try:
         sample_fh = open(sample_file,'r')
         feathermodules.samples.append(sample_fh.read())
         sample_fh.close()
      except:
         print 'Something went wrong. Sorry! Please try again.'
      finally:
         readline.set_completer(ishellCompleter)

class ImportManualEntryCommand(Command):
   def run(self, line):
      feathermodules.samples.append(raw_input('Please enter your sample: ').strip())


class ImportResultsCommand(Command):
   def run(self, line):
      if not feathermodules.results:
         print 'Last module failed to produce results.'
      elif feathermodules.results == True:
         print 'Last module succeeded, but did not return results.'
      else:
         print 'Last results (long values may be truncated):'
         print '-'*80
         for i in range(len(feathermodules.results)):
            print '{0:d}: {1:60s}'.format(i, repr(feathermodules.results[i]))
      
      selection = raw_input('\nPlease enter your selection by number, or \'all\' for all: ')
      try:
         if selection == 'all':
            feathermodules.samples.extend(feathermodules.results)
         elif int(selection) < len(feathermodules.results):
            feathermodules.samples.append(feathermodules.results[int(selection)])
         else:
            print 'Invalid entry, please try again.'
      except ValueError:
         print 'Invalid entry, please try again.'
         

class ImportClearCommand(Command):
   def run(self, line):
      feathermodules.samples = []

class ImportCommand(Command):
   def args(self):
      return ['multifile', 'singlefile', 'manualentry', 'results', 'clear']

import_sample = ImportCommand('import', help='Import samples for analysis', dynamic_args=True)

import_multifile = ImportMultiFileCommand(
   'multifile',
   help='Import multiple newline-separated samples from one file',
   dynamic_args=True)
import_singlefile = ImportSingleFileCommand(
   'singlefile',
   help='Import a single file as a raw sample',
   dynamic_args=True)
import_manualentry = ImportManualEntryCommand(
   'manualentry',
   help='Manually enter a single sample',
   dynamic_args=True)
import_results = ImportResultsCommand(
   'results',
   help='Import last module\'s results as samples',
   dynamic_args=True)
import_clear = ImportClearCommand(
   'clear',
   help='Clear current sample set',
   dynamic_args=True)

import_sample.addChild(import_multifile)
import_sample.addChild(import_singlefile)
import_sample.addChild(import_manualentry)
import_sample.addChild(import_results)
import_sample.addChild(import_clear)

# use
class UseCommand(Command):
   def args(self):
      return feathermodules.module_list.keys()
   def run(self, line):
      if line.split()[-1] not in feathermodules.module_list.keys():
         print 'Invalid module selection. Please try again.'
      else:
         feathermodules.selected_attack = feathermodules.module_list[ line.split()[-1] ]
         feathermodules.selected_attack_name = line.split()[-1]
         feathermodules.current_options = feathermodules.selected_attack['options']
    

use = UseCommand('use', help='Select the module to use', dynamic_args=True)

# analyze
class AnalyzeCommand(Command):
   def run(self, line):
      if len(feathermodules.samples) == 0:
         print 'No loaded samples. Please use the \'import\' command.'
         return False
      print '[+] Analyzing samples...'
      analysis_results = ca.analyze_ciphertext(feathermodules.samples, verbose=True)
      if analysis_results['decoded_ciphertexts'] != feathermodules.samples:
         feathermodules.samples = analysis_results['decoded_ciphertexts']
      print ''
      print '[+] Suggested modules:'
      for attack in feathermodules.module_list.keys():
         if len(set(feathermodules.module_list[attack]['keywords']) & set(analysis_results['keywords'])) > 0:
            print '   {0:<20} - {1:<57}'.format(attack, feathermodules.module_list[attack]['description'])
   
analyze = AnalyzeCommand('analyze', help='Analyze/decode samples', dynamic_args=True)


# autopwn
class AutopwnCommand(Command):
   def run(self, line):
      if len(feathermodules.samples) == 0:
         print 'No loaded samples. Please use the \'import\' command.'
         return False
      print '[+] Analyzing samples...'
      analysis_results = ca.analyze_ciphertext(feathermodules.samples, verbose=True)
      if analysis_results['decoded_ciphertexts'] != feathermodules.samples:
         feathermodules.samples = analysis_results['decoded_ciphertexts']
      for attack in feathermodules.module_list.keys():
         if len(set(feathermodules.module_list[attack]['keywords']) & set(analysis_results['keywords'])) > 0:
            print 'Running module: %s' % attack
            feathermodules.current_options = feathermodules.module_list[attack]['options']
            print feathermodules.module_list[attack]['attack_function'](feathermodules.samples)
   
autopwn = AutopwnCommand('autopwn', help='Analyze samples and run all attacks', dynamic_args=True)


# search
class SearchCommand(Command):
   def run(self, line):
      matching_modules = []
      search_param = line.split()[-1].lower()
      for attack in feathermodules.module_list.keys():
         if attack.lower().find(search_param) != -1:
            matching_modules.append(attack)
         elif feathermodules.module_list[attack]['description'].lower().find(search_param) != -1:
            matching_modules.append(attack)
         elif search_param in feathermodules.module_list[attack]['keywords']:
            matching_modules.append(attack)
      for module in matching_modules:
         print "%s - %s" % (module, feathermodules.module_list[module]['description'])
      
search = SearchCommand('search', help='Search module names and descriptions by keyword')


# samples
class SamplesCommand(Command):
   def run(self, line):
      print '-' * 60
      for sample in feathermodules.samples:
         print repr(sample)
      print '-' * 60

samples = SamplesCommand('samples', help='Show samples')


# modules
class ModulesCommand(Command):
   def run(self, line):
      for attack in feathermodules.module_list.keys():
         print "%s - %s" % (attack, feathermodules.module_list[attack]['description'])

modules = ModulesCommand('modules', help='Show all available modules')


# run
class RunCommand(Command):
   def run(self, line):
      if len(feathermodules.samples) == 0:
         print 'No loaded samples. Please use the \'import\' command.'
         return False
      elif feathermodules.selected_attack_name not in feathermodules.module_list.keys():
         print 'Invalid module selection. Please use the \'use\' command.'
         return False
      feathermodules.results = feathermodules.selected_attack['attack_function'](feathermodules.samples)

run = RunCommand('run', help='Run the currently selected module')


# options
class OptionsCommand(Command):
   def run(self, line):
      if feathermodules.selected_attack_name not in feathermodules.module_list.keys():
         print 'Please select a valid module first.'
         return False
      else:
         print ''
         print '{0:^60}'.format('Currently selected module: ' + feathermodules.selected_attack_name)
         print '-' * 60
         if len(feathermodules.selected_attack['options'].items()) == 0:
            print 'No options to configure.'
         else:
            for option, default in feathermodules.selected_attack['options'].items():
               try:
                  print '{0:<20}{1:>40}'.format(option, feathermodules.current_options[option])
               except:
                  print '{0:<20}{1:>40}'.format(option, default)


# set
class SetCommand(Command):
   def run(self, line):
      line_split = line.split()
      # set option_name value
      if not (len(line_split) == 2 and '=' in line_split[1]):
         print 'Usage: set <option>=<value>'
         return False
      option = line_split[1].split('=')[0]
      value = line_split[1].split('=')[1]
      feathermodules.current_options[option] = value
   def args(self):
      return feathermodules.selected_attack['options'].keys()


# unset
class UnsetCommand(Command):
   def run(self, line):
      line_split = line.split()
      # unset option_name
      if len(line_split) != 2:
         print 'Usage: unset <option>'
         return False
      option = line_split[1]
      feathermodules.current_options[option] = feathermodules.selected_attack['options'][option]
   def args(self):
      return feathermodules.selected_attack['options'].keys()

# results
class ResultsCommand(Command):
   def run(self, line):
      if not feathermodules.results:
         print 'Last module failed to produce results.'
      elif feathermodules.results == True:
         print 'Last module succeeded, but did not return results.'
      else:
         print 'Last results (long values may be truncated):'
         print '-'*80
         for i in range(len(feathermodules.results)):
            print '{0:d}: {1:60s}'.format(i, repr(feathermodules.results[i]))
      

set_command = SetCommand('set', help='Set an option (i.e., "set num_answers=3"', dynamic_args=True)
unset = UnsetCommand('unset', help='Revert an option to its default value', dynamic_args=True)
options = OptionsCommand('options', help='Show the current option values', dynamic_args=True)
results = ResultsCommand('results', help='Show the results from the last module run')


# Build the console
fd_console = Console(prompt='\nFeatherDuster', prompt_delim='>')

fd_console.addChild(import_sample)
fd_console.addChild(use)
fd_console.addChild(analyze)
fd_console.addChild(autopwn)
fd_console.addChild(search)
fd_console.addChild(samples)
fd_console.addChild(modules)
fd_console.addChild(run)
fd_console.addChild(options)
fd_console.addChild(set_command)
fd_console.addChild(unset)
fd_console.addChild(results)


#--------
# Main menu
#

for filename in sys.argv[1:]:
   if filename in ['-h', '--help']:
      print 'Usage: python featherduster.py [ciphertext file 1] ... [ciphertext file n]'
      exit()
   try:
      sample_fh = open(filename,'r')
      feathermodules.samples.append(sample_fh.read())
      sample_fh.close()
   except:
      continue

print """Welcome to FeatherDuster!<commit_msg>Added: Interactive mode<commit_after>'''
FeatherDuster - A wizard-like interface to cryptanalib
Author: <NAME>

FeatherDuster is a tool for brushing away magical crypto fairy dust.
'''

import sys
import readline
import completer
from ishell.console import Console
from ishell.command import Command

try:
   from IPython import embed
except ImportError:
   import code

   def embed():
      vars = globals()
      vars.update(locals())
      shell = code.InteractiveConsole(vars)
      shell.interact()

import feathermodules

feathermodules.samples = []
feathermodules.results = False
feathermodules.selected_attack_name = ''
feathermodules.current_options = {}

from feathermodules.stream import *
from feathermodules.block import *
from feathermodules.classical import *
from feathermodules.auxiliary import *
from feathermodules.custom import *
from feathermodules.pubkey import *

import cryptanalib as ca

# import
class ImportMultiFileCommand(Command):
   def run(self, line):
      ishellCompleter = readline.get_completer()
      readline.set_completer_delims(' \t\n;')
      readline.parse_and_bind("tab: complete")
      readline.set_completer(completer.complete)

      sample_file = raw_input('Please enter the filename you want to open: ')
      try:
         sample_fh = open(sample_file,'r')
         feathermodules.samples.extend([sample.strip() for sample in sample_fh.readlines()])
         sample_fh.close()
         feathermodules.samples = filter(lambda x: x != '' and x != None, feathermodules.samples)
      except:
         print 'Something went wrong. Sorry! Please try again.'
      finally:
         readline.set_completer(ishellCompleter)

class ImportSingleFileCommand(Command):
   def run(self, line):
      ishellCompleter = readline.get_completer()
      readline.set_completer_delims(' \t\n;')
      readline.parse_and_bind("tab: complete")
      readline.set_completer(completer.complete)

      sample_file = raw_input('Please enter the filename you want to open: ')
      try:
         sample_fh = open(sample_file,'r')
         feathermodules.samples.append(sample_fh.read())
         sample_fh.close()
      except:
         print 'Something went wrong. Sorry! Please try again.'
      finally:
         readline.set_completer(ishellCompleter)

class ImportManualEntryCommand(Command):
   def run(self, line):
      feathermodules.samples.append(raw_input('Please enter your sample: ').strip())


class ImportResultsCommand(Command):
   def run(self, line):
      if not feathermodules.results:
         print 'Last module failed to produce results.'
      elif feathermodules.results == True:
         print 'Last module succeeded, but did not return results.'
      else:
         print 'Last results (long values may be truncated):'
         print '-'*80
         for i in range(len(feathermodules.results)):
            print '{0:d}: {1:60s}'.format(i, repr(feathermodules.results[i]))
      
      selection = raw_input('\nPlease enter your selection by number, or \'all\' for all: ')
      try:
         if selection == 'all':
            feathermodules.samples.extend(feathermodules.results)
         elif int(selection) < len(feathermodules.results):
            feathermodules.samples.append(feathermodules.results[int(selection)])
         else:
            print 'Invalid entry, please try again.'
      except ValueError:
         print 'Invalid entry, please try again.'
         

class ImportClearCommand(Command):
   def run(self, line):
      feathermodules.samples = []

class ImportCommand(Command):
   def args(self):
      return ['multifile', 'singlefile', 'manualentry', 'results', 'clear']

import_sample = ImportCommand('import', help='Import samples for analysis', dynamic_args=True)

import_multifile = ImportMultiFileCommand(
   'multifile',
   help='Import multiple newline-separated samples from one file',
   dynamic_args=True)
import_singlefile = ImportSingleFileCommand(
   'singlefile',
   help='Import a single file as a raw sample',
   dynamic_args=True)
import_manualentry = ImportManualEntryCommand(
   'manualentry',
   help='Manually enter a single sample',
   dynamic_args=True)
import_results = ImportResultsCommand(
   'results',
   help='Import last module\'s results as samples',
   dynamic_args=True)
import_clear = ImportClearCommand(
   'clear',
   help='Clear current sample set',
   dynamic_args=True)

import_sample.addChild(import_multifile)
import_sample.addChild(import_singlefile)
import_sample.addChild(import_manualentry)
import_sample.addChild(import_results)
import_sample.addChild(import_clear)

# console
class ConsoleCommand(Command):
   def run(self, line):
      ishellCompleter = readline.get_completer()
      embed()
      readline.set_completer(ishellCompleter)


console = ConsoleCommand('console', help='Opens an interactive prompt', dynamic_args=True)

# use
class UseCommand(Command):
   def args(self):
      return feathermodules.module_list.keys()
   def run(self, line):
      if line.split()[-1] not in feathermodules.module_list.keys():
         print 'Invalid module selection. Please try again.'
      else:
         feathermodules.selected_attack = feathermodules.module_list[ line.split()[-1] ]
         feathermodules.selected_attack_name = line.split()[-1]
         feathermodules.current_options = feathermodules.selected_attack['options']
    

use = UseCommand('use', help='Select the module to use', dynamic_args=True)

# analyze
class AnalyzeCommand(Command):
   def run(self, line):
      if len(feathermodules.samples) == 0:
         print 'No loaded samples. Please use the \'import\' command.'
         return False
      print '[+] Analyzing samples...'
      analysis_results = ca.analyze_ciphertext(feathermodules.samples, verbose=True)
      if analysis_results['decoded_ciphertexts'] != feathermodules.samples:
         feathermodules.samples = analysis_results['decoded_ciphertexts']
      print ''
      print '[+] Suggested modules:'
      for attack in feathermodules.module_list.keys():
         if len(set(feathermodules.module_list[attack]['keywords']) & set(analysis_results['keywords'])) > 0:
            print '   {0:<20} - {1:<57}'.format(attack, feathermodules.module_list[attack]['description'])
   
analyze = AnalyzeCommand('analyze', help='Analyze/decode samples', dynamic_args=True)


# autopwn
class AutopwnCommand(Command):
   def run(self, line):
      if len(feathermodules.samples) == 0:
         print 'No loaded samples. Please use the \'import\' command.'
         return False
      print '[+] Analyzing samples...'
      analysis_results = ca.analyze_ciphertext(feathermodules.samples, verbose=True)
      if analysis_results['decoded_ciphertexts'] != feathermodules.samples:
         feathermodules.samples = analysis_results['decoded_ciphertexts']
      for attack in feathermodules.module_list.keys():
         if len(set(feathermodules.module_list[attack]['keywords']) & set(analysis_results['keywords'])) > 0:
            print 'Running module: %s' % attack
            feathermodules.current_options = feathermodules.module_list[attack]['options']
            print feathermodules.module_list[attack]['attack_function'](feathermodules.samples)
   
autopwn = AutopwnCommand('autopwn', help='Analyze samples and run all attacks', dynamic_args=True)


# search
class SearchCommand(Command):
   def run(self, line):
      matching_modules = []
      search_param = line.split()[-1].lower()
      for attack in feathermodules.module_list.keys():
         if attack.lower().find(search_param) != -1:
            matching_modules.append(attack)
         elif feathermodules.module_list[attack]['description'].lower().find(search_param) != -1:
            matching_modules.append(attack)
         elif search_param in feathermodules.module_list[attack]['keywords']:
            matching_modules.append(attack)
      for module in matching_modules:
         print "%s - %s" % (module, feathermodules.module_list[module]['description'])
      
search = SearchCommand('search', help='Search module names and descriptions by keyword')


# samples
class SamplesCommand(Command):
   def run(self, line):
      print '-' * 60
      for sample in feathermodules.samples:
         print repr(sample)
      print '-' * 60

samples = SamplesCommand('samples', help='Show samples')


# modules
class ModulesCommand(Command):
   def run(self, line):
      for attack in feathermodules.module_list.keys():
         print "%s - %s" % (attack, feathermodules.module_list[attack]['description'])

modules = ModulesCommand('modules', help='Show all available modules')


# run
class RunCommand(Command):
   def run(self, line):
      if len(feathermodules.samples) == 0:
         print 'No loaded samples. Please use the \'import\' command.'
         return False
      elif feathermodules.selected_attack_name not in feathermodules.module_list.keys():
         print 'Invalid module selection. Please use the \'use\' command.'
         return False
      feathermodules.results = feathermodules.selected_attack['attack_function'](feathermodules.samples)

run = RunCommand('run', help='Run the currently selected module')


# options
class OptionsCommand(Command):
   def run(self, line):
      if feathermodules.selected_attack_name not in feathermodules.module_list.keys():
         print 'Please select a valid module first.'
         return False
      else:
         print ''
         print '{0:^60}'.format('Currently selected module: ' + feathermodules.selected_attack_name)
         print '-' * 60
         if len(feathermodules.selected_attack['options'].items()) == 0:
            print 'No options to configure.'
         else:
            for option, default in feathermodules.selected_attack['options'].items():
               try:
                  print '{0:<20}{1:>40}'.format(option, feathermodules.current_options[option])
               except:
                  print '{0:<20}{1:>40}'.format(option, default)


# set
class SetCommand(Command):
   def run(self, line):
      line_split = line.split()
      # set option_name value
      if not (len(line_split) == 2 and '=' in line_split[1]):
         print 'Usage: set <option>=<value>'
         return False
      option = line_split[1].split('=')[0]
      value = line_split[1].split('=')[1]
      feathermodules.current_options[option] = value
   def args(self):
      return feathermodules.selected_attack['options'].keys()


# unset
class UnsetCommand(Command):
   def run(self, line):
      line_split = line.split()
      # unset option_name
      if len(line_split) != 2:
         print 'Usage: unset <option>'
         return False
      option = line_split[1]
      feathermodules.current_options[option] = feathermodules.selected_attack['options'][option]
   def args(self):
      return feathermodules.selected_attack['options'].keys()

# results
class ResultsCommand(Command):
   def run(self, line):
      if not feathermodules.results:
         print 'Last module failed to produce results.'
      elif feathermodules.results == True:
         print 'Last module succeeded, but did not return results.'
      else:
         print 'Last results (long values may be truncated):'
         print '-'*80
         for i in range(len(feathermodules.results)):
            print '{0:d}: {1:60s}'.format(i, repr(feathermodules.results[i]))
      

set_command = SetCommand('set', help='Set an option (i.e., "set num_answers=3"', dynamic_args=True)
unset = UnsetCommand('unset', help='Revert an option to its default value', dynamic_args=True)
options = OptionsCommand('options', help='Show the current option values', dynamic_args=True)
results = ResultsCommand('results', help='Show the results from the last module run')


# Build the console
fd_console = Console(prompt='\nFeatherDuster', prompt_delim='>')

fd_console.addChild(import_sample)
fd_console.addChild(console)
fd_console.addChild(use)
fd_console.addChild(analyze)
fd_console.addChild(autopwn)
fd_console.addChild(search)
fd_console.addChild(samples)
fd_console.addChild(modules)
fd_console.addChild(run)
fd_console.addChild(options)
fd_console.addChild(set_command)
fd_console.addChild(unset)
fd_console.addChild(results)


#--------
# Main menu
#

for filename in sys.argv[1:]:
   if filename in ['-h', '--help']:
      print 'Usage: python featherduster.py [ciphertext file 1] ... [ciphertext file n]'
      exit()
   try:
      sample_fh = open(filename,'r')
      feathermodules.samples.append(sample_fh.read())
      sample_fh.close()
   except:
      continue

print """Welcome to FeatherDuster!
{'fraction_non_alphanumeric': 0.11329170383586083, 'fraction_numerical': 0.005027978266158462, 'mean_word_length': 3.601305970149254, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1825215', 'n_tokens_mistral': 7575, 'n_tokens_neox': 6923, 'n_words': 1722}