import logging import requests from abc import ABC, abstractmethod from typing import List from concurrent.futures import ThreadPoolExecutor, wait from random import triangular from time import sleep, time from blindpie.request import IRequest from blindpie.defaults import DEFAULT_MAX_INTERVAL, DEFAULT_MAX_THREADS LOGGER = logging.getLogger(__name__) class ITarget(ABC): """An interface representing a target website. """ @abstractmethod def get_url(self) -> str: """Returns the URL of this target. :return: str -- the URL of this target """ @abstractmethod def get_response_time(self, request: IRequest) -> float: """Returns the response time of the target to a request in ms. :param request: IRequest -- the request to make :return: int -- the response time to the request in ms :raises: TargetUnavailableException -- when the target seems to be unavailable """ pass @abstractmethod def get_response_times(self, requests_: List[IRequest], max_interval: int = DEFAULT_MAX_INTERVAL, max_threads: int = DEFAULT_MAX_THREADS) -> List[float]: """Returns the response times of the target to multiple requests in ms. :param requests_: List[IRequest] -- the list of requests to make :param max_interval: int -- the max time to wait between each request in ms :param max_threads: int -- the max number of requests to make concurrently :return: List[int] -- the response times to the requests in ms :raises: TargetUnavailableException -- when the target seems to be unavailable """ pass class TargetUnavailableException(Exception): """Exception thrown when a target seems to be unavailable. """ def __init__(self, target: ITarget, request: IRequest, status: str): """Instantiates the exception from a target, the request, and its status. :param target: ITarget -- the target which is unavailable :param request: IRequest -- the request which raised the exception :param status: str -- the target status """ self.target: ITarget = target self.request: IRequest = request self.status: str = status def __str__(self): return "Target '{:s}' was unavailable ('{:s}') during request '{:s}'".format(self.target.get_url(), self.status, str(self.request)) class Target(ITarget): """A concrete representation of a target website. """ def __init__(self, url: str): """Instantiates a target from its URL. :param url: str -- the target URL """ self.url = url def _get_response_time(self, request: IRequest) -> float: """Returns the response time of the target to a request in ms. :param request: IRequest -- the request to make :return: int -- the response time to the request in ms :raises: TargetUnavailableException -- when the target seems to be unavailable """ try: start_time = time() response = requests.request(url=self.get_url(), params=request.get_params(), method=request.get_method(), headers=request.get_headers()) end_time = time() - start_time response.raise_for_status() LOGGER.debug("Target response time: {:f} ms".format(end_time * 1000)) return end_time * 1000 except requests.HTTPError as e: raise TargetUnavailableException(target=self, request=request, status=str(e.response.status_code)) def get_url(self) -> str: return self.url def get_response_time(self, request: IRequest) -> float: return self._get_response_time(request) def get_response_times(self, requests_: List[IRequest], max_interval: int = DEFAULT_MAX_INTERVAL, max_threads: int = DEFAULT_MAX_THREADS) -> List[float]: with ThreadPoolExecutor(max_workers=max_threads) as thread_pool: threads = list() for r in requests_: delay = triangular(max_interval / 2, max_interval) sleep(delay / 1000) LOGGER.debug("Delayed for: {:f} ms".format(delay)) threads.append(thread_pool.submit(self._get_response_time, request=r)) wait(threads) return [t.result() for t in threads]