=begin
                  Arachni
  Copyright (c) 2010-2012 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>

  This is free software; you can copy and distribute and modify
  this program under the term of the GPL v2.0 License
  (See LICENSE file for details)

=end


#
# Vulnerability class.
#
# It represents a detected vulnerability.
#
#
# @author: Tasos "Zapotek" Laskos
#                                      <tasos.laskos@gmail.com>
#                                      <zapotek@segfault.gr>
# @version: 0.1.1
#
module Arachni

class Issue

    #
    # Holds constants to describe the {Issue#severity} of a
    # vulnerability.
    #
    module Severity
        HIGH          = 'High'
        MEDIUM        = 'Medium'
        LOW           = 'Low'
        INFORMATIONAL = 'Informational'
    end

    #
    # Holds constants to describe the {Issue#elem} of a
    # vulnerability.
    #
    module Element
        LINK    = 'link'
        FORM    = 'form'
        COOKIE  = 'cookie'
        HEADER  = 'header'
        BODY    = 'body'
        PATH    = 'path'
        SERVER  = 'server'
    end

    #
    # The name of the issue
    #
    # @return    [String]
    #
    attr_accessor :name

    #
    # The module that detected the issue
    #
    # @return    [String]    the name of the module
    #
    attr_accessor :mod_name

    #
    # The vulnerable HTTP variable
    #
    # @return    [String]    the name of the http variable
    #
    attr_accessor :var

    #
    # The vulnerable URL
    #
    # @return    [String]
    #
    attr_accessor :url

    #
    # The headers exchanged during the attack
    #
    # @return [Hash<String, Hash>]  request and reply headers
    #
    attr_accessor :headers

    #
    # The HTML response of the attack
    #
    # @return [String]  the html response of the attack
    #
    attr_accessor :response

    #
    # The injected data that revealed the issue
    #
    # @return    [String]
    #
    attr_accessor :injected

    #
    # The string that identified the issue
    #
    # @return    [String]
    #
    attr_accessor :id

    #
    # The regexp that identified the issue
    #
    # @return    [String]
    #
    attr_reader   :regexp

    #
    # The data that was matched by the regexp
    #
    # @return    [String]
    #
    attr_accessor :regexp_match

    #
    # The vulnerable element, link, form or cookie
    #
    # @return    [String]
    #
    attr_accessor :elem

    #
    # HTTP method
    #
    # @return    [String]
    #
    attr_accessor :method

    #
    # The description of the issue
    #
    # @return    [String]
    #
    attr_accessor :description

    #
    # References related to the issue
    #
    # @return    [Hash]
    #
    attr_accessor :references

    #
    # The CWE ID number of the issue
    #
    # @return    [String]
    #
    attr_accessor :cwe

    #
    # The CWE URL of the issue
    #
    # @return    [String]
    #
    attr_accessor :cwe_url

    #
    # To be assigned a constant form {Severity}
    #
    # @see Severity
    #
    # @return    [String]
    #
    attr_accessor :severity

    #
    # The CVSS v2 score
    #
    # @return    [String]
    #
    attr_accessor :cvssv2

    #
    # A brief text informing the user how to remedy the situation
    #
    # @return    [String]
    #
    attr_accessor :remedy_guidance

    #
    # A code snippet showing the user how to remedy the situation
    #
    # @return    [String]
    #
    attr_accessor :remedy_code

    #
    # Placeholder variable to be populated by {AuditStore#prepare_variations}
    #
    # @see AuditStore#prepare_variations
    #
    attr_accessor :variations

    #
    # Is manual verification required?
    #
    # @return  [Bool]
    #
    attr_accessor :verification

    #
    # The Metasploit module that can exploit the vulnerability.
    #
    # ex. exploit/unix/webapp/php_include
    #
    # @return  [String]
    #
    attr_accessor :metasploitable

    attr_reader   :opts

    attr_accessor :internal_modname
    attr_accessor :tags
    attr_accessor :_hash

    #
    # Sets up the instance attributes
    #
    # @param    Hash    configuration hash
    #                     Usually the returned data of a module's
    #                     info() method for the references
    #                     merged with a name=>value pair hash holding
    #                     class attributes
    #
    def initialize( opts = {} )
        @verification = false

        opts.each {
            |k, v|
            begin
                send( "#{k.to_s.downcase}=", encode( v ) )
            rescue Exception => e
            end
        }

        opts[:issue].each {
            |k, v|
            begin
                send( "#{k.to_s.downcase}=", encode( v ) )
            rescue Exception => e
            end
        } if opts[:issue]

        if opts[:headers] && opts[:headers][:request]
            @headers[:request] = {}.merge( opts[:headers][:request] )
        end

        if opts[:headers] && opts[:headers][:response].is_a?( Hash )
            @headers[:response] = {}.merge( opts[:headers][:response] )
        end

        if( @cwe )
            @cwe_url = "http://cwe.mitre.org/data/definitions/" + @cwe + ".html"
        end

        @mod_name   = opts[:name]
        @references = opts[:references] || {}
    end

    def regexp=( regexp )
        return if !regexp
        @regexp = regexp.to_s
    end

    def opts=( hash )
        return if !hash
        hash[:regexp] = hash[:regexp].to_s
        hash[:match]  ||= false
        @opts = hash.dup
    end

    def []( k )
        instance_variable_get( "@#{k.to_s}".to_sym )
    end

    def []=( k, v )
        v= encode( v )
        begin
            send( "#{k.to_s}=", v )
        rescue
            instance_variable_set( "@#{k.to_s}".to_sym, v )
        end
    end

    def to_h
        h = {}
        each_pair {
            |k, v|
            h[k] = v
        }
        h
    end

    def each
        self.instance_variables.each {
            |var|
            yield( { normalize_name( var ) => instance_variable_get( var ) } )
        }
    end

    def each_pair
        self.instance_variables.each {
            |var|
            yield normalize_name( var ), instance_variable_get( var )
        }
    end

    def remove_instance_var( var )
        remove_instance_variable( var )
    end

    private

    def encode( str )
        return str if !str || !str.is_a?( String )
        str.encode( 'UTF-8', :invalid => :replace, :undef => :replace )
    end

    def normalize_name( name )
        name.to_s.gsub( /@/, '' )
    end

end
end