require 'net/http'
require 'uri'
require 'json'
require 'ipaddr'
require 'socket'
require 'openssl'
require 'base64'

TAU_PORT = 37279
TAU_TIMEOUT = 5

YAML_DATA_ARRAY_BLOCK = "data"
YAML_DATA_ARRAY_DELIMITER = ";"



COMMAND_GET_LOCAL_IP = "-get_local_ip"
COMMAND_GET_BROWSER_IP_LIST = "-get_browser_ip_list"
COMMAND_CHECK_IP = "-check_ip"
COMMAND_GET_INFO = "-get_info"
COMMAND_GET_LOG = "-get_log"
COMMAND_GET_CONFIG = "-get_config"
COMMAND_SET_CONFIG = "-set_config"
COMMAND_REMOVE_CONFIG = "-remove_config"
COMMAND_GET_LICENSE = "-get_license"
COMMAND_SET_LICENSE = "-set_license"
COMMAND_REMOVE_LICENSE = "-remove_license"
COMMAND_GET_FILE = "-get_file"
COMMAND_SET_FILE = "-set_file"
COMMAND_REMOVE_FILE = "-remove_file"
COMMAND_RESTART = "-restart"
COMMAND_CREATE_KEYS = "-create_keys"
COMMAND_ENCRYPT_CONFIG = "-encrypt_config"
COMMAND_DECRYPT_CONFIG = "-decrypt_config"
COMMAND_GET_PASSWORD_HASH = "-get_password_hash"
COMMAND_CHECK_PASSWORD = "-check_password"



def help
    print "
    You must setup some parameters :

    - Get local this system IP adress :
    Usage: #{__FILE__} #{COMMAND_GET_LOCAL_IP}
    Example: #{__FILE__} #{COMMAND_GET_LOCAL_IP}

    - Get the IP addresses of the devices running the browser :
    Usage: #{__FILE__} #{COMMAND_GET_BROWSER_IP_LIST} starting_ip end_ip
    Example: #{__FILE__} #{COMMAND_GET_BROWSER_IP_LIST} 1 254

    - Check IP adress for Tau Browser HTTP server :
    Usage: #{__FILE__} #{COMMAND_CHECK_IP} ip
    Example: #{__FILE__} #{COMMAND_CHECK_IP} 192.168.50.208

    - Receive Info from Tau Browser HTTP server :
    Usage: #{__FILE__} #{COMMAND_GET_INFO} ip
    Example: #{__FILE__} #{COMMAND_GET_INFO} 192.168.50.208

    - Receive application's logfile from Tau Browser HTTP server and save to file :
    Usage: #{__FILE__} #{COMMAND_GET_LOG} ip filename
    Example: #{__FILE__} #{COMMAND_GET_LOG} 192.168.50.208 mylog.txt

    - Receive Config.xml from Tau Browser HTTP server and save to file:
    Usage: #{__FILE__} #{COMMAND_GET_CONFIG} ip filename
    Example: #{__FILE__} #{COMMAND_GET_CONFIG} 192.168.50.208 MyConfig.xml

    - Upload Config.xml to Tau Browser (password is optional) :
    Usage: #{__FILE__} #{COMMAND_SET_CONFIG} ip filename password
    Example: #{__FILE__} #{COMMAND_SET_CONFIG} 192.168.50.208 Config.xml 12345

    - Remove Config.xml on Tau Browser's side (password is optional) :
    Usage: #{__FILE__} #{COMMAND_REMOVE_CONFIG} ip password
    Example: #{__FILE__} #{COMMAND_REMOVE_CONFIG} 192.168.50.208 12345

    - Receive license from Tau Browser HTTP server and save to file:
    Usage: #{__FILE__} #{COMMAND_GET_LICENSE} ip filename
    Example: #{__FILE__} #{COMMAND_GET_LICENSE} 192.168.50.208 mylicense.txt

    - Upload license to Tau Browser (password is optional) :
    Usage: #{__FILE__} #{COMMAND_SET_LICENSE} ip filename password
    Example: #{__FILE__} #{COMMAND_SET_LICENSE} 192.168.50.208 mylicense.txt 12345

    - Remove license on Tau Browser's side (password is optional) :
    Usage: #{__FILE__} #{COMMAND_REMOVE_LICENSE} ip password
    Example: #{__FILE__} #{COMMAND_REMOVE_LICENSE} 192.168.50.208 12345

    - Receive file located on device from Tau Browser HTTP server and save to file:
    Usage: #{__FILE__} #{COMMAND_GET_FILE} ip filepath_on_device filepath_to_save
    Example: #{__FILE__} #{COMMAND_GET_FILE} 192.168.50.208 /sdcard/tau/browser/index.html index.html

    - Upload file to device via Tau Browser (password is optional) :
    Usage: #{__FILE__} #{COMMAND_SET_FILE} ip filepath_local filepath_on_device password
    Example: #{__FILE__} #{COMMAND_SET_FILE} 192.168.50.208 index.html /sdcard/tau/browser/index.html 12345

    - Remove file on device by Tau Browser (password is optional) :
    Usage: #{__FILE__} #{COMMAND_REMOVE_FILE} ip filepath_on_device password
    Example: #{__FILE__} #{COMMAND_REMOVE_FILE} 192.168.50.208 /sdcard/tau/browser/index.html 12345


    - Restart Tau Browser (password is optional) :
    Usage: #{__FILE__} #{COMMAND_RESTART} ip password
    Example: #{__FILE__} #{COMMAND_RESTART} 192.168.50.208 12345

    - Calculate encrypted hash for password (recommend save to file) :
    Usage: #{__FILE__} #{COMMAND_GET_PASSWORD_HASH} password
    Example: #{__FILE__} #{COMMAND_GET_PASSWORD_HASH} 12345 >> my_psw_hash.txt

    - Check password for encrypted hash (hash must be in text file because it can contain special characters restricted for command line) :
    Usage: #{__FILE__} #{COMMAND_CHECK_PASSWORD} password file_with_password_hash
    Example: #{__FILE__} #{COMMAND_CHECK_PASSWORD} 12345 my_psw_hash.txt

    - Create pair of private and public keys :
    Usage: #{__FILE__} #{COMMAND_CREATE_KEYS} private_key_filename public_key_filename
    Example: #{__FILE__} #{COMMAND_CREATE_KEYS} myprivate.pem mypublic.pem

    - Encrypt Config.xml :
    Usage: #{__FILE__} #{COMMAND_ENCRYPT_CONFIG} private_key_filename input_file output_file
    Example: #{__FILE__} #{COMMAND_ENCRYPT_CONFIG} myprivate.pem RAWConfig.xml Config.xml

    - Decrypt Config.xml :
    Usage: #{__FILE__} #{COMMAND_DECRYPT_CONFIG} public_key_filename input_file output_file
    Example: #{__FILE__} #{COMMAND_DECRYPT_CONFIG} mypublic.pem Config.xml DecryptedConfig.xml

  "
end





def get_current_ip
    addr_infos = Socket.ip_address_list
    addr_infos = addr_infos.reject( &:ipv4_loopback? )
              .reject( &:ipv6_loopback? )
    ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}
    return ip.ip_address
end

def get_tau_browser_ip_list(base_ip, start_addr, end_addr, timeout)
    ip_list = []
    port = TAU_PORT

    ip_parts = base_ip.split(".")

    ip_base = "" + ip_parts[0] + "." + ip_parts[1] + "." + ip_parts[2] + "."

    threads = []

    for i in (start_addr..end_addr)
        threads << Thread.new(i) do |_i|
            ip = ip_base+_i.to_s
            uri = URI.parse("http://"+ip+":"+port.to_s+"/tau_browser/check")
            #puts "$$$ uri = "+uri.to_s
            #puts " IP "+ip+ "   checking ..."

            begin
                header = {'Content-Type': 'text/plain'}
                http = Net::HTTP.start(uri.host, uri.port, :open_timeout => timeout, :read_timeout => timeout)
                request = Net::HTTP::Get.new(uri.request_uri, header)
                response = http.request(request)
                #puts "response = "+response.to_s
                if response.instance_of? Net::HTTPOK
                    #puts "   TAU DETECTED !"
                    ip_list << ip
                end
                http.finish
            rescue => e
                #puts "$$$ Exception e = "+e.to_s
            end
        end
    end

    threads.each { |thr| thr.join }

    return ip_list
end


def tau_browser_get_request(ip, path)
    body = ""
    error_msg = nil
    uri = URI.parse("http://"+ip+":"+TAU_PORT.to_s+path)
    begin
        header = {'Content-Type': 'text/plain'}
        http = Net::HTTP.start(uri.host, uri.port, :open_timeout => TAU_TIMEOUT, :read_timeout => TAU_TIMEOUT)
        request = Net::HTTP::Get.new(uri.request_uri, header)
        response = http.request(request)
        if response.instance_of? Net::HTTPOK
                body = response.read_body
        else
            error_msg = "Server return "+response.code
        end
        http.finish
    rescue => e
        error_msg = e.message
    end
    if error_msg
        raise error_msg
    end
    return body
end

def tau_browser_check(ip)
    tau_browser_get_request(ip, "/tau_browser/check")
end


def tau_browser_get_info(ip)
    return tau_browser_get_request(ip, "/tau_browser/get_info")
end

def tau_browser_get_log(ip)
    return tau_browser_get_request(ip, "/tau_browser/get_log")
end

def tau_browser_get_config(ip)
    return tau_browser_get_request(ip, "/tau_browser/get_config")
end

def tau_browser_remove_config(ip, password)
    ur = "/tau_browser/remove_config"
    if password
         ur = ur + "?password="+password
    end
    return tau_browser_get_request(ip, ur)
end


def tau_browser_set_config(ip, config_content, password)
    error_msg = nil
    ur = "http://"+ip+":"+TAU_PORT.to_s+"/tau_browser/set_config"
    if password
         ur = ur + "?password="+password
    end
    uri = URI.parse(ur)
    begin
        header = {'Content-Type': 'text/plain'}
        http = Net::HTTP.start(uri.host, uri.port, :open_timeout => TAU_TIMEOUT, :read_timeout => TAU_TIMEOUT)
        request = Net::HTTP::Post.new(uri.request_uri, header)
        request.body = config_content
        response = http.request(request)
        if response.instance_of? Net::HTTPOK
        else
            error_msg = "Server return "+response.code
        end
        http.finish
    rescue => e
        error_msg = e.message
    end
    if error_msg
        raise error_msg
    end
end

def tau_browser_get_license(ip)
    return tau_browser_get_request(ip, "/tau_browser/get_license")
end

def tau_browser_remove_license(ip, password)
    ur = "/tau_browser/remove_license"
    if password
         ur = ur + "?password="+password
    end
    return tau_browser_get_request(ip, ur)
end


def tau_browser_set_license(ip, license_content, password)
    error_msg = nil
    ur = "http://"+ip+":"+TAU_PORT.to_s+"/tau_browser/set_license"
    if password
         ur = ur + "?password="+password
    end
    uri = URI.parse(ur)
    begin
        header = {'Content-Type': 'text/plain'}
        http = Net::HTTP.start(uri.host, uri.port, :open_timeout => TAU_TIMEOUT, :read_timeout => TAU_TIMEOUT)
        request = Net::HTTP::Post.new(uri.request_uri, header)
        request.body = license_content
        response = http.request(request)
        if response.instance_of? Net::HTTPOK
        else
            error_msg = "Server return "+response.code
        end
        http.finish
    rescue => e
        error_msg = e.message
    end
    if error_msg
        raise error_msg
    end
end

def tau_browser_get_file(ip, filepath)
    #puts "encoded filepath = "+URI::Parser.new.escape(filepath)
    return tau_browser_get_request(ip, "/tau_browser/get_file?filepath="+URI::Parser.new.escape(filepath))
end

def tau_browser_remove_file(ip, filepath, password)
    ur = "/tau_browser/remove_file"
    ur = ur + "?filepath="+URI::Parser.new.escape(filepath)
    if password
         ur = ur + "&password="+password
    end
    return tau_browser_get_request(ip, ur)
end

def tau_browser_set_file(ip, filepath, file_content, password)
    error_msg = nil
    ur = "http://"+ip+":"+TAU_PORT.to_s+"/tau_browser/set_file"
    ur = ur + "?filepath="+URI::Parser.new.escape(filepath)
    if password
         ur = ur + "&password="+password
    end
    uri = URI.parse(ur)
    begin
        #header = {'Content-Type': 'application/octet-stream'}
        header = {'Content-Type': 'text/plain'}
        http = Net::HTTP.start(uri.host, uri.port, :open_timeout => TAU_TIMEOUT, :read_timeout => TAU_TIMEOUT)
        request = Net::HTTP::Post.new(uri.request_uri, header)
        request.body = Base64.encode64(file_content)
        #request.payload = file_content
        response = http.request(request)
        if response.instance_of? Net::HTTPOK
        else
            error_msg = "Server return "+response.code
        end
        http.finish
    rescue => e
        error_msg = e.message
    end
    if error_msg
        raise error_msg
    end
end

def tau_browser_restart(ip, password)
    ur = "/tau_browser/restart"
    if password
         ur = ur + "?password="+password
    end
    tau_browser_get_request(ip, ur)
end



def generate_key(private_file, public_file)
    puts " generate key START"

    key = OpenSSL::PKey::RSA.new(2048)
    public_key = key.public_key

    puts " Save private key into "+private_file
    File.write(private_file, key.to_pem)
    puts " Save public key into "+public_file
    File.write(public_file, public_key.to_pem)

    puts " generate key FINISH"

end

def do_private_encrypt(private_key_file, input_file, output_file)
    puts " private encrypt START"

    puts " Load private key from "+private_key_file
    private_key = OpenSSL::PKey::RSA.new (File.read private_key_file)

    puts " Load input data from "+input_file
    input_data = File.open(input_file).read
    #puts "input_data = "+input_data.to_s

    puts " Preparing input data"
    input_data_64 = Base64.encode64(input_data)
    #puts "input_data_64 = "+input_data_64.to_s

    input_array_of_strings = input_data_64.chars.each_slice(128).map(&:join)
    #puts "input_array_of_strings = "+input_array_of_strings.to_s

    output_yaml = {}
    output_yaml[YAML_DATA_ARRAY_BLOCK] = []

    puts " Encrypt data"
    input_array_of_strings.each do |str|
        #puts "    str = "+str.to_s

        output_data = private_key.private_encrypt(str, OpenSSL::PKey::RSA::PKCS1_PADDING)
        #puts "    output_data = "+output_data.to_s

        output_data_64 = Base64.encode64(output_data)
        #puts "    output_data_64 = "+output_data_64.to_s

        output_yaml[YAML_DATA_ARRAY_BLOCK] << output_data_64
    end
    output_data = output_yaml[YAML_DATA_ARRAY_BLOCK].join(YAML_DATA_ARRAY_DELIMITER)

    puts " Save encrypted data into "+output_file
    #File.write(output_file, output_yaml.to_yaml)

    encrypted_config = "<?xml version = \"1.0\"?>
    <Configuration>
        <Config value=\""+output_data+"\"/>
    </Configuration>"

    File.write(output_file, encrypted_config)

    puts " private encrypt FINISH"
end

def do_public_decrypt(public_key_file, input_file, output_file)

    #require 'rexml'
    require 'rexml/document'

    puts " public decrypt START"

    puts " Load public key from "+public_key_file
    public_key = OpenSSL::PKey::RSA.new File.read public_key_file

    puts " Load encrypted data from "+input_file
    #input_file_yaml = YAML::load_file(input_file)

    input_file_raw = File.read input_file
    input_file_yaml = ""

    doc = REXML::Document.new( input_file_raw )
    node = REXML::XPath.first(doc, '//Configuration/Config')
    if node
        value = node.attributes.get_attribute('value').value
        if value
            input_file_yaml = value
        else
            raise "encrypted node not contain value !"
        end
    end

    #input_file_yaml = File.read input_file
    #input_array = input_file_yaml[YAML_DATA_ARRAY_BLOCK]
    input_array = input_file_yaml.split(YAML_DATA_ARRAY_DELIMITER)

    output_array = []

    puts " Decrypt data"
    input_array.each do |str|
        decrypted_str = public_key.public_decrypt(Base64.decode64(str), OpenSSL::PKey::RSA::PKCS1_PADDING)
        output_array << decrypted_str
    end

    puts " Preparing data"
    output_64 = output_array.join
    output_data = Base64.decode64(output_64)

    puts " Save decrypted data into "+output_file
    File.write(output_file, output_data)

    puts " public decrypt FINISH"
end




if (ARGV.size == 3) && ARGV[0].to_s.downcase == COMMAND_GET_BROWSER_IP_LIST
    start_addr = ARGV[1]
    end_addr = ARGV[2]
    ip_addrs = get_tau_browser_ip_list(get_current_ip,start_addr,end_addr, TAU_TIMEOUT)
    ip_addrs.each {|ip| puts ip}
    exit
end


if (ARGV.size == 1) && ARGV[0].to_s.downcase == COMMAND_GET_LOCAL_IP
    puts get_current_ip
    exit
end

if (ARGV.size == 2) && ARGV[0].to_s.downcase == COMMAND_CHECK_IP
    ip = ARGV[1]
    begin
        tau_browser_check(ip)
        puts "1"
    rescue => e
        puts "0"
    end
    exit
end

if (ARGV.size == 2) && ARGV[0].to_s.downcase == COMMAND_GET_INFO
    ip = ARGV[1]
    begin
        puts tau_browser_get_info(ip)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if (ARGV.size == 3) && ARGV[0].to_s.downcase == COMMAND_GET_LOG
    ip = ARGV[1]
    filename = ARGV[2]
    begin
        log = tau_browser_get_log(ip)
        File.write(filename, log)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if (ARGV.size == 3) && ARGV[0].to_s.downcase == COMMAND_GET_LOG
    ip = ARGV[1]
    filename = ARGV[2]
    begin
        log = tau_browser_get_log(ip)
        File.write(filename, log)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if (ARGV.size == 3) && ARGV[0].to_s.downcase == COMMAND_GET_CONFIG
    ip = ARGV[1]
    filename = ARGV[2]
    begin
        cfg = tau_browser_get_config(ip)
        File.write(filename, cfg)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if ((ARGV.size == 3) || (ARGV.size == 4)) && ARGV[0].to_s.downcase == COMMAND_SET_CONFIG
    ip = ARGV[1]
    filename = ARGV[2]
    password = nil
    if ARGV.size == 4
        password = ARGV[3]
    end
    begin
        config = File.read filename
        cfg = tau_browser_set_config(ip, config, password)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if ((ARGV.size == 2) || (ARGV.size == 3)) && ARGV[0].to_s.downcase == COMMAND_REMOVE_CONFIG
    ip = ARGV[1]
    password = nil
    if ARGV.size == 3
        password = ARGV[2]
    end
    begin
        tau_browser_remove_config(ip, password)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if (ARGV.size == 3) && ARGV[0].to_s.downcase == COMMAND_GET_LICENSE
    ip = ARGV[1]
    filename = ARGV[2]
    begin
        lic = tau_browser_get_license(ip)
        File.write(filename, lic)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if ((ARGV.size == 3) || (ARGV.size == 4)) && ARGV[0].to_s.downcase == COMMAND_SET_LICENSE
    ip = ARGV[1]
    filename = ARGV[2]
    password = nil
    if ARGV.size == 4
        password = ARGV[3]
    end
    begin
        lic = File.read filename
        cfg = tau_browser_set_license(ip, lic, password)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if ((ARGV.size == 2) || (ARGV.size == 3)) && ARGV[0].to_s.downcase == COMMAND_REMOVE_LICENSE
    ip = ARGV[1]
    password = nil
    if ARGV.size == 3
        password = ARGV[2]
    end
    begin
        tau_browser_remove_license(ip, password)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end


if (ARGV.size == 4) && ARGV[0].to_s.downcase == COMMAND_GET_FILE
    ip = ARGV[1]
    filepath_on_device = ARGV[2]
    filepath_local = ARGV[3]
    begin
        f = tau_browser_get_file(ip, filepath_on_device)
        File.write(filepath_local, f)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if ((ARGV.size == 3) || (ARGV.size == 4)) && ARGV[0].to_s.downcase == COMMAND_REMOVE_FILE
    ip = ARGV[1]
    password = nil
    filepath = ARGV[2]
    if ARGV.size == 4
        password = ARGV[3]
    end
    begin
        tau_browser_remove_file(ip, filepath, password)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if ((ARGV.size == 4) || (ARGV.size == 5)) && ARGV[0].to_s.downcase == COMMAND_SET_FILE
    ip = ARGV[1]
    filename = ARGV[2]
    filepath = ARGV[3]
    password = nil
    if ARGV.size == 5
        password = ARGV[4]
    end
    begin
        file = File.open(filename, "rb")
        lic = file.read.force_encoding("ASCII-8BIT")
        cfg = tau_browser_set_file(ip, filepath, lic, password)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end

if ((ARGV.size == 2) || (ARGV.size == 3)) && ARGV[0].to_s.downcase == COMMAND_RESTART
    ip = ARGV[1]
    password = nil
    if ARGV.size == 3
        password = ARGV[2]
    end
    begin
        tau_browser_restart(ip, password)
    rescue => e
        puts "$$$ Error: "+e.message
    end
    exit
end


if (ARGV.size == 2) && ARGV[0].to_s.downcase == COMMAND_GET_PASSWORD_HASH
    begin
      require 'bcrypt'
      password = ARGV[1]
      password_hash = BCrypt::Password.create(password).to_s
      puts password_hash
    rescue LoadError
      puts 'Error: BCrypt gem not installed !'
      puts 'Please install it by next command:'
      puts 'gem install bcrypt'
    end
    exit
end

if (ARGV.size == 3) && ARGV[0].to_s.downcase == COMMAND_CHECK_PASSWORD
    begin
      require 'bcrypt'
      password = ARGV[1]
      password_hash_file = ARGV[2]
      password_hash = File.read password_hash_file
      #puts "$$$ = "+password_hash
      password_bcrypt = BCrypt::Password.new(password_hash.strip)
      if password_bcrypt == password
          puts "1"
      else
          puts "0"
      end
    rescue LoadError
      puts 'Error: BCrypt gem not installed !'
      puts 'Please install it by next command:'
      puts 'gem install bcrypt'
    end
    exit
end


if (ARGV.size == 3) && ARGV[0].to_s.downcase == COMMAND_CREATE_KEYS
    private_file = ARGV[1]
    public_file = ARGV[2]
    generate_key(private_file, public_file)
    exit
end

if (ARGV.size == 4) && ARGV[0].to_s.downcase == COMMAND_ENCRYPT_CONFIG
    private_file = ARGV[1]
    input_file = ARGV[2]
    output_file = ARGV[3]
    do_private_encrypt(private_file, input_file, output_file)
    exit
end

if (ARGV.size == 4) && ARGV[0].to_s.downcase == COMMAND_DECRYPT_CONFIG
    public_file = ARGV[1]
    input_file = ARGV[2]
    output_file = ARGV[3]
    do_public_decrypt(public_file, input_file, output_file)
    exit
end



help
