=begin
    Copyright 2010-2013 Tasos Laskos <tasos.laskos@gmail.com>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
=end

#
# Provides a notice for issues uncovered by timing attacks when the affected audited
# pages returned unusually high response times to begin with.
#
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
#
# @version 0.1.5
#
class Arachni::Plugins::TimingAttacks < Arachni::Plugin::Base

    is_distributable

    # look for issue by tag name
    TAG = 'timing'

    # response times of a page must be greater or equal to this
    # in order to be considered
    TIME_THRESHOLD = 0.6

    REMARK = 'This issue was discovered using a timing-attack but the audited ' +
        'page was exhibiting unusually high response times to begin with. ' +
        'This could be an indication that the logged issue is a false positive.'

    def prepare
        @times   = {}
        @counter = {}

        # run for each response as it arrives
        framework.http.add_on_complete do |res|
            # we don't care about non OK responses
            next if res.code != 200

            # let's hope for a proper and clean parse but be prepared for
            # all hell to break loose too...
            begin
                url = uri_parse( res.effective_url ).up_to_path
            rescue => e
                next
            end

            @counter[url] ||= @times[url] ||= 0

            # add up all request times for a specific path
            @times[url] += res.start_transfer_time

            # add up all requests for each path
            @counter[url] += 1
        end

        wait_while_framework_running
    end

    def run
        avg = {}

        # calculate average request time for each path
        @times.each_pair { |url, time| avg[url] = time / @counter[url] }

        inconclusive = framework.modules.issues.map.with_index do |issue, idx|
            response_time = avg[ uri_parse( issue.url ).up_to_path ]

            next if !issue.tags || !issue.tags.includes_tags?( TAG ) ||
                !response_time || response_time < TIME_THRESHOLD

            issue.add_remark :meta_analysis, REMARK

            # Requires manual verification.
            issue.verification = true

            {
                'hash'   => issue.digest,
                'index'  => idx + 1,
                'url'    => issue.url,
                'name'   => issue.name,
                'var'    => issue.var,
                'elem'   => issue.elem,
                'method' => issue.method
            }
        end.compact

        register_results( inconclusive ) if !inconclusive.empty?
    end

    def self.merge( results )
        results.flatten
    end

    def self.info
        {
            name:        'Timing attack anomalies',
            description: %q{Analyzes the scan results and logs issues that used timing attacks
                while the affected web pages demonstrated an unusually high response time;
                a situation which renders the logged issues inconclusive or (possibly) false positives.

                Pages with high response times usually include heavy-duty processing
                which makes them prime targets for Denial-of-Service attacks.},
            author:      'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
            version:     '0.1.5',
            tags:        %w(anomaly timing attacks meta)
        }
    end

end