#!/usr/bin/env ruby
# frozen_string_literal: true

# Ruby internal
require 'shellwords' # for shellescape cmd
# Project internal
require 'ctf_party'
require 'ctf_party/version'
# External
require 'docopt'

cmd_whitelist = {
  # ctf-party commands
  alternatecase: 'Change one characte on two upcase and the other downcase',
  bin2dec: 'Convert a binary string to decimal',
  bin2hex: 'Encode an binary string to a hexadecimal string',
  bin2str: 'Alias for from_bin',
  dec2bin: 'Convert a decimal string to binary',
  dec2hex: 'Encode an decimal string to a hexadecimal string',
  dec2str: 'Alias for from_dec',
  defang_domain: 'Defang domain name',
  defang_email: 'Defang email address',
  defang_ip: 'Defang IP address',
  defang_uri: 'Defang URI',
  from_b64: 'Decode the string from base64',
  from_bin: 'Decode a binary string',
  from_dec: 'Decode a decimal string (decimal to hexadecimal then hexadecimal to string)',
  from_hex: 'Decode a hexadecimal string',
  from_hexip: 'Decode a hexadecimal IPv4 string into a dotted decimal one',
  from_hexipv4: 'Decode a hexadecimal IPv4 string into a dotted decimal one',
  from_hexipv6: 'Decode a hexadecimal IPv6 string into a the double-dotted hexadecimal format',
  hex2bin: 'Encode an hexadecimal string to a binary string',
  hex2dec: 'Encode an hexadecimal string to a decimal string',
  hex2str: 'Alias for from_hex',
  htmlescape: 'HTML escape the string',
  htmlunescape: 'HTML unescape the string',
  istrip: 'Remove leading and trailing whitespace but also all inner whitespace',
  leet: 'Transform into leet speak (l337 5p34k)',
  md5: 'Calculate the md5 hash of the string',
  randomcase: 'Change the case of characters randomly',
  refang_domain: 'Refang domain name',
  refang_email: 'Refang email address',
  refang_ip: 'Refang IP address',
  refang_uri: 'Refang URI',
  rmd160: 'Calculate the RIPEMD-160 hash of the string',
  rot13: 'Encrypt / Decrypt the string with Caesar cipher with a shift of 13',
  sha1: 'Calculate the sha1 hash of the string',
  sha2: 'Calculate the sha2 hash of the string',
  sha2_256: 'Alias for sha2 with bitlen of 256',
  sha2_384: 'Alias for sha2 with bitlen of 384',
  sha2_512: 'Alias for sha2 with bitlen of 512',
  str2bin: 'Alias for to_bin',
  str2dec: 'Alias for to_dec',
  str2hex: 'Alias for to_hex',
  to_b64: 'Encode the string into base64',
  to_bin: 'Encode a string into binary',
  to_dec: 'Encode a string into decimal (string to hexadecimal then hexadecimal to decimal)',
  to_hex: 'Encode a string into hexadecimal',
  to_hexip: 'Encode a dotted decimal IPv4 into a hexadecimal one',
  to_hexipv4: 'Encode a dotted decimal IPv4 into a hexadecimal one',
  urldecode: 'URL-decode the string (RFC 2396)',
  urldecode_component: 'URL-decode the URL component string (RFC 3986)',
  urldecode_data: 'URL-decode the form data (application/x-www-form-urlencoded) string',
  urlencode: 'URL-encode the string (RFC 2396)',
  urlencode_component: 'URL-encode the URL component string (RFC 3986)',
  urlencode_data: 'URL-encode form data (application/x-www-form-urlencoded) string',
  # native string commands
  bytesize: 'https://rubyapi.org/3.2/o/string#method-i-bytesize',
  capitalize: 'https://rubyapi.org/3.2/o/string#method-i-capitalize',
  chomp: 'https://rubyapi.org/3.2/o/string#method-i-chomp',
  chop: 'https://rubyapi.org/3.2/o/string#method-i-chop',
  downcase: 'https://rubyapi.org/3.2/o/string#method-i-downcase',
  dump: 'https://rubyapi.org/3.2/o/string#method-i-dump',
  hex: 'https://rubyapi.org/3.2/o/string#method-i-hex',
  inspect: 'https://rubyapi.org/3.2/o/string#method-i-inspect',
  length: 'https://rubyapi.org/3.2/o/string#method-i-length',
  lstrip: 'https://rubyapi.org/3.2/o/string#method-i-lstrip',
  reverse: 'https://rubyapi.org/3.2/o/string#method-i-reverse',
  rstrip: 'https://rubyapi.org/3.2/o/string#method-i-rstrip',
  scrub: 'https://rubyapi.org/3.2/o/string#method-i-scrub',
  shellescape: 'https://rubyapi.org/3.2/o/string#method-i-shellescape',
  size: 'https://rubyapi.org/3.2/o/string#method-i-size',
  squeeze: 'https://rubyapi.org/3.2/o/string#method-i-squeeze',
  strip: 'https://rubyapi.org/3.2/o/string#method-i-strip',
  succ: 'https://rubyapi.org/3.2/o/string#method-i-succ',
  swapcase: 'https://rubyapi.org/3.2/o/string#method-i-swapcase',
  undump: 'https://rubyapi.org/3.2/o/string#method-i-undump',
  unicode_normalize: 'https://rubyapi.org/3.2/o/string#method-i-unicode_normalize',
  upcase: 'https://rubyapi.org/3.2/o/string#method-i-upcase'
}

doc = <<~DOCOPT
  ctf-party v#{CTFParty::VERSION} by noraj

  Usage:
    ctf-party <string> <cmd>... [--row --file] [--debug]
    ctf-party --list-commands [--debug]
    ctf-party -h | --help
    ctf-party --version

  Parameters:
    <string>              The string to manipulate, read from STDIN if equal to "-"
    <cmd>                 Command to apply to the string, cf. --list-commands

  Options:
    -l, --list-commands   List available commands (see https://noraj.github.io/ctf-party/yard/String.html)
    -r, --row             Apply the transformation to each row
    -f, --file            Interpret the string as a filename, if file doesn't exist it will still be treated as a string
    --debug               Display arguments
    -h, --help            Show this screen
    --version             Show version

  Examples:
    ctf-party 'security' to_hex
    ctf-party 'NzQ2Zjc0NmY=' from_b64 hex2bin
    curl -s https://example.org | ctf-party - htmlescape
    seq 1 10 | ctf-party - dec2hex hex2bin --row
    cut -d : -f 1 /etc/passwd | ctf-party - randomcase --row
    ctf-party /etc/passwd str2hex --row --file
DOCOPT

begin
  args = Docopt.docopt(doc, version: CTFParty::VERSION)
  # use case 1, using the tool
  puts args if args['--debug']
  if args['<string>']
    args['<string>'] = $stdin.read.chomp if args['<string>'] == '-'
    args['<string>'] = File.read(args['<string>']) if args['--file'] && File.exist?(args['<string>'])
    wrong_cmd = args['<cmd>'] - cmd_whitelist.keys.map(&:to_s)
    if wrong_cmd.empty?
      if args['--row']
        output = ''
        args['<string>'].each_line(chomp: true) do |line|
          output_line = line
          args['<cmd>'].each do |cmd|
            output_line = output_line.public_send(cmd)
          end
          output += "#{output_line}\n"
        end
      else
        output = args['<string>']
        args['<cmd>'].each do |cmd|
          output = output.public_send(cmd)
        end
      end
      puts output
    else
      abort "Those commands don't exist: #{wrong_cmd}"
    end
  elsif args['--list-commands']
    cmd_whitelist.each do |k, v|
      puts "#{k.to_s.ljust(25)}#{v}"
    end
  end
  # use case 2, help: already handled by docopt
  # use case 3, version: already handled by docopt
rescue Docopt::Exit => e
  puts e.message
end
