from __future__ import annotations
import re
from typing import Any, Iterator
from lib.core.data import options
from lib.core.decorators import locked
from lib.core.settings import (
SCRIPT_PATH,
EXTENSION_TAG,
EXCLUDE_OVERWRITE_EXTENSIONS,
EXTENSION_RECOGNITION_REGEX,
)
from lib.core.structures import OrderedSet
from lib.parse.url import clean_path
from lib.utils.common import lstrip_once
from lib.utils.file import FileUtils
def get_blacklists() -> dict[int, Dictionary]:
blacklists = {}
for status in [400, 403, 500]:
blacklist_file_name = FileUtils.build_path(SCRIPT_PATH, "db")
blacklist_file_name = FileUtils.build_path(
blacklist_file_name, f"{status}_blacklist.txt"
)
if not FileUtils.can_read(blacklist_file_name):
continue
blacklists[status] = Dictionary(
files=[blacklist_file_name],
is_blacklist=True,
)
return blacklists
class Dictionary:
def __init__(self, **kwargs: Any) -> None:
self._index = 0
self._items = self.generate(**kwargs)
self._extra_index = 0
self._extra = []
@property
def index(self) -> int:
return self._index
@locked
def __next__(self) -> str:
if len(self._extra) > self._extra_index:
self._extra_index += 1
return self._extra[self._extra_index - 1]
elif len(self._items) > self._index:
self._index += 1
return self._items[self._index - 1]
else:
raise StopIteration
def __contains__(self, item: str) -> bool:
return item in self._items
def __getstate__(self) -> tuple[list[str], int]:
return self._items, self._index, self._extra, self._extra_index
def __setstate__(self, state: tuple[list[str], int]) -> None:
self._items, self._index, self._extra, self._extra_index = state
def __iter__(self) -> Iterator[str]:
return iter(self._items)
def __len__(self) -> int:
return len(self._items)
def generate(self, files: list[str] = [], is_blacklist: bool = False) -> list[str]:
"""
Dictionary.generate() behaviour
Classic dirsearch wordlist:
1. If %EXT% keyword is present, append one with each extension REPLACED.
2. If the special word is no present, append line unmodified.
Forced extensions wordlist (NEW):
This type of wordlist processing is a mix between classic processing
and DirBuster processing.
1. If %EXT% keyword is present in the line, immediately process as "classic dirsearch" (1).
2. If the line does not include the special word AND is NOT terminated by a slash,
append one with each extension APPENDED (line.ext) and ONLY ONE with a slash.
3. If the line does not include the special word and IS ALREADY terminated by slash,
append line unmodified.
"""
wordlist = OrderedSet()
re_ext_tag = re.compile(EXTENSION_TAG, re.IGNORECASE)
for dict_file in files:
for line in FileUtils.get_lines(dict_file):
line = lstrip_once(line, "/")
if not self.is_valid(line):
continue
if EXTENSION_TAG in line.lower():
for extension in options["extensions"]:
newline = re_ext_tag.sub(extension, line)
wordlist.add(newline)
else:
wordlist.add(line)
if is_blacklist:
continue
if (
options["force_extensions"]
and "." not in line
and not line.endswith("/")
):
wordlist.add(line + "/")
for extension in options["extensions"]:
wordlist.add(f"{line}.{extension}")
elif (
options["overwrite_extensions"]
and not line.endswith(options["extensions"] + EXCLUDE_OVERWRITE_EXTENSIONS)
and "?" not in line
and "#" not in line
and re.search(EXTENSION_RECOGNITION_REGEX, line)
):
base = line.split(".")[0]
for extension in options["extensions"]:
wordlist.add(f"{base}.{extension}")
if not is_blacklist:
altered_wordlist = OrderedSet()
for path in wordlist:
for pref in options["prefixes"]:
if (
not path.startswith(("/", pref))
):
altered_wordlist.add(pref + path)
for suff in options["suffixes"]:
if (
not path.endswith(("/", suff))
and "?" not in path
and "#" not in path
):
altered_wordlist.add(path + suff)
if altered_wordlist:
wordlist = altered_wordlist
if options["lowercase"]:
return list(map(str.lower, wordlist))
elif options["uppercase"]:
return list(map(str.upper, wordlist))
elif options["capitalization"]:
return list(map(str.capitalize, wordlist))
else:
return list(wordlist)
def is_valid(self, path: str) -> bool:
if not path or path.startswith("#"):
return False
cleaned_path = clean_path(path)
if cleaned_path.endswith(
tuple(f".{extension}" for extension in options["exclude_extensions"])
):
return False
return True
def add_extra(self, path) -> None:
if path in self._items or path in self._extra:
return
self._extra.append(path)
def reset(self) -> None:
self._index = self._extra_index = 0
self._extra.clear()