#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (https://wapiti.sourceforge.io)
# Copyright (C) 2018-2021 Nicolas Surribas
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
from httpx import RequestError

from wapitiCore.attack.attack import Attack
from wapitiCore.net.web import Request


class mod_methods(Attack):
    """
    Detect uncommon HTTP methods (like PUT) that may be allowed by a script.
    """

    name = "methods"
    PRIORITY = 6
    KNOWN_METHODS = {"GET", "POST", "OPTIONS", "HEAD", "TRACE"}
    do_get = False
    do_post = False
    excluded_path = set()

    def must_attack(self, request: Request):
        return request.path not in self.excluded_path

    async def attack(self, request: Request):
        page = request.path
        self.excluded_path.add(page)

        option_request = Request(
            page,
            "OPTIONS",
            referer=request.referer,
            link_depth=request.link_depth
        )

        if self.verbose == 2:
            print("[+] {}".format(option_request))

        try:
            response = await self.crawler.async_send(option_request)
        except RequestError:
            self.network_errors += 1
            return

        if 200 <= response.status < 400:
            methods = response.headers.get("allow", '').upper().split(',')
            methods = {method.strip() for method in methods if method.strip()}
            interesting_methods = sorted(methods - self.KNOWN_METHODS)

            if interesting_methods:
                self.log_orange("---")
                self.log_orange(
                    "Interesting methods allowed on {}: {}".format(
                        page,
                        ", ".join(interesting_methods)
                    )
                )
                self.log_orange("---")
