require "dawn/engine"
require 'ruby_parser'

module Dawn
  class Sinatra
    include Dawn::Engine

    attr_reader :sinks
    attr_reader :appname

    # mount_point is the mounting point for this Sinatra application. It's
    # filled up only in padrino engines
    attr_reader :mount_point

    def initialize(dir=nil, mp=nil)
      super(dir, "sinatra")
      @appname = detect_appname(self.target)
      error! if self.appname == ""
      @views = detect_views
      @sinks = detect_sinks(self.appname) unless self.appname == ""
      @reflected_xss = detect_reflected_xss unless self.appname == "" || !@views
      @mount_point = (mp.nil?)? "" : mp
    end

    # TODO: appname should be hopefully autodetect from config.ru
    def detect_appname(target)
      return "app.rb" if File.exist?(File.join(self.target, "app.rb"))
      return "application.rb" if File.exist?(File.join(self.target, "application.rb"))
      file_array = Dir.glob(File.join("#{target}", "*.rb"))
      return file_array[0] if ! file_array.nil? and file_array.count == 1
      return "" # gracefully failure
    end

    def detect_reflected_xss
      ret = []
      @views.each do |v|
        view_content = File.read(v[:filename])
        @sinks.each do |sink|
          if view_content.match(sink[:sink_name])
            sink[:sink_view] = v[:filename]
            ret << sink
          end
        end
      end
      ret
    end

    def detect_sinks(appname=nil)
      ret = []
      appname = "app.rb" if appname.nil? and self.appname == ""
      app_rb = File.readlines(File.join(self.target, appname)) if File.exist?(File.join(self.target, appname))
      return [] if app_rb.nil?

      parser = RubyParser.new

      app_rb.each_with_index do |line, i|
        line = line.chomp

        unless line.match(/params/).nil?

          begin
            t = parser.parse(line)

            # a[1] = params["foo"]
            #
            if (! t.nil? and t.sexp_type == :attrasgn)
              body = t.sexp_body.to_a

              if is_assignement_from_params?(body, :attrasgn)

                if body[0][0] == :call
                  sink_name=body[0][2].to_s unless body[1].to_s == "[]=" 
                  sink_name="#{body[2].to_a[1].to_s}" if body[1].to_s == "[]=" # the sink is assigned to an Array

                  sink_source = "#{body[3].to_a[1][2].to_s}[#{body[3].to_a[3][1].to_s}]"

                  ret << {:sink_name=>sink_name, :sink_kind=>:params, :sink_line=>i+1, :sink_source=>sink_source, :sink_file=>appname, :sink_evidence=>line}
                end
                if body[0][0] == :ivar
                  sink_name=body[0][1].to_s
                  sink_pos=body[2][1].to_i
                  sink_source=body[3][3][1]

                  ret << {:sink_name=>sink_name, :sink_kind=>:params, :sink_line=>i+1, :sink_source=>sink_source, :sink_file=>appname, :sink_evidence=>line}
                end

              end
            end

            # a = params["foo"]
            if (! t.nil? and t.sexp_type == :iasgn)
              body = t.sexp_body.to_a
              if is_assignement_from_params?(body, :iasgn)
                sink_name = body[0].to_s
                sink_source = "#{body[1][3][1].to_s}"
                ret << {:sink_name=>sink_name, :sink_kind=>:params, :sink_line=>i+1, :sink_source=>sink_source, :sink_file=>appname, :sink_evidence=>line}
              end
            end
          rescue Racc::ParseError => e
            # TODO: we must pass logger instance to engines if we want some error message
            # For now... silently discard parse errors
            return []
          end
        end

      end

      ret
    end


    def is_assignement_from_params?(body, kind)
      return ( ! body[3].nil? and body[3].to_a[1][2].to_s == "params") if kind == :attrasgn
      return ( ! body[1].nil? and ! body[1][1].nil? and body[1][1][2].to_s == "params") if kind == :iasgn
    end

    def detect_views
      return build_view_array(File.join(self.target, "views")) if File.exist?(File.join(self.target, "views"))
      []
    end

    # e = Haml::Engine.new(File.read(template))
    # e.precompiled  and grep for format_script


  end
end
