Added some russian docs. Changed prototype behaviour. Added __init__ args to config. Now JSONStorage supports dynamic database schemes defined

in config.
This commit is contained in:
Zloooy 2019-11-13 19:15:54 +03:00
parent 30c8f235fb
commit 8e1fc5a369
23 changed files with 433 additions and 85 deletions

View File

@ -12,8 +12,8 @@ INSTALLATION
------------
Just run
```bash
git clone http://github.com/ChronosX88/PySca.git
cd PySca
git clone http://github.com/Zloooy/PySca.git
cd PyNesca
pip install -r requirements.txt
```

View File

@ -10,8 +10,8 @@ PySca - сетевой сканер, переписанный на Python
------------
Введите в терминале
```bash
git clone http://github.com/ChronosX88/PySca.git
cd PySca
git clone http://github.com/Zloooy/PySca.git
cd PyNesca
pip install -r requirements.txt
```
@ -22,3 +22,13 @@ pip install -r requirements.txt
python main.py
```
Находясь в корневой папке PySca
НАСТРОЙКА
------------
Тут [описание синтаксиса config.py](./docs/config.ru.md)
ХОЧУ СДЕЛАТЬ СВОЙ МОДУЛЬ ДЛЯ PySca
-------------
Тут [общие требования к оформлению модуля](./docs/developing_custom_module.md)
Тут [описание функций конкретных модулей](./docs/classes.ru.md)

View File

@ -1,7 +1,56 @@
#modules selection
#modules and selection and init args setup
config = {
"parser" : "GDocsHashParser",
"address_generator" : "GDocsAddressGenerator",
"scanner" : "URLScanner",
"storage" : "GDocsStorage"
"parser" :
{
"name":"GDocsHashParser",
"init_args":{}
},
"address_generator" :
{
"name":"GDocsAddressGenerator",
"init_args":{}
},
"scanner" :
{
"name":"GDocsScanner",
"init_args":{}
},
"storage" :
{
"name":"JSONStorage",
"init_args":
{
"path":"results.json",
"json_scheme":{
"status":
{
"gdoc_prefix":
[
{
"@hash": "gdoc_hash",
"@title": "gdoc_title"
}
]
}
}
}
}
}
'''scheme for url scanner
{
"status":
{
"url"
}
}'''
'''scheme for port scanner
{
"ipv4_str":
{
"port_status_str":
{
"port"
}
}
}'''

View File

@ -6,19 +6,19 @@ from PyQt5.Qt import QThread, pyqtSignal
from PyQt5.QtCore import QObject, pyqtSlot
from config import config
from inspect import isfunction
from communication.communication_utils import complitable_functions, get_converted_arguments
from communication.communication_utils import complitable_functions, get_converted_arguments, get_argument_annotations, get_return_annotations
CoreModel = import_utils.import_class("modules/network_scan/%s.py" %
config["scanner"])
config["scanner"]["name"])
Parser = import_utils.import_class("modules/address_generation/%s.py" %
config["parser"]
config["parser"]["name"]
)
IpGenerator = import_utils.import_class(
"modules/address_generation/%s.py" %
config["address_generator"]
config["address_generator"]["name"]
)
JSONStorage = import_utils.import_class("modules/storage/%s.py" %
config["storage"])
config["storage"]["name"])
convert_table = ConvertTable()
for func in import_utils.import_matching(
"modules/convert_functions/",
@ -36,15 +36,42 @@ for function in [
msg = "%s is not complitable with %s"
print(msg % (function, previous))
previous = function
convert_for_parser = convert_table.get_metaconverter(
{'address_field','port_field'},
get_argument_annotations(Parser.parse_fields)
)
convert_for_address_generator = convert_table.get_metaconverter(
get_return_annotations(Parser.parse_fields),
get_argument_annotations(IpGenerator.set_parsed_fields)
)
convert_for_scanner = convert_table.get_metaconverter(
get_return_annotations(IpGenerator.get_next_address),
get_argument_annotations(CoreModel.scan_address)
)
convert_for_address_generator_reverse = convert_table.get_metaconverter(
get_return_annotations(IpGenerator.get_next_address).union(get_return_annotations(CoreModel.scan_address)),
get_argument_annotations(IpGenerator.get_next_address)
)
convert_for_storage = None
class MainPresenter:
def __init__(self, ui):
self.ui = ui
self.threads = []
self.isScanEnabled = False
self.parser = Parser()
self.parser = Parser(*get_converted_arguments(Parser.__init__,
config['parser']['init_args'], convert_table))
#needed config to specify path
self.storage = JSONStorage("results.json")
print(*get_converted_arguments(JSONStorage.__init__,
config["storage"]["init_args"], convert_table))
self.storage = JSONStorage(*get_converted_arguments(JSONStorage.__init__,
config["storage"]["init_args"], convert_table))
print(get_argument_annotations(self.storage.put_responce))
global convert_for_storage
convert_for_storage = convert_table.get_metaconverter(
get_return_annotations(IpGenerator.get_next_address).union(get_return_annotations(CoreModel.scan_address)),
get_argument_annotations(self.storage.put_responce)
)
self.exit_lock = RLock()
def startScan(self, ipRanges, portsStr, threadNumber, timeout):
@ -52,25 +79,21 @@ class MainPresenter:
addresses = None
parser_args = {'port_field':portsStr, 'address_field':ipRanges}
fields = self.parser.parse_fields(
*get_converted_arguments(
self.parser.parse_fields,
parser_args,
convert_table
*convert_for_parser(parser_args)
)
)
self.scanner = CoreModel(timeout)
config["scanner"]["init_args"]["timeout"] = timeout
self.scanner = CoreModel(*get_converted_arguments(CoreModel.__init__,
config["scanner"]["init_args"], convert_table))
if CoreModel.INDEPENDENT_THREAD_MANAGEMENT:
addresses = self.parser.get_all_addresses(ipRanges)
self.ip_generator = PlugAddressGenerator(addresses, ports)
threadNumber = 1
else:
self.ip_generator = IpGenerator()
self.ip_generator = IpGenerator(
*get_converted_arguments(IpGenerator.__init__,
config["address_generator"]["init_args"], convert_table))
self.ip_generator.set_parsed_fields(
*get_converted_arguments(
self.ip_generator.set_parsed_fields,
fields,
convert_table
)
*convert_for_address_generator(fields)
)
threadNumber = int(threadNumber)
print("thread %i number set" % threadNumber)
@ -142,30 +165,18 @@ class ScanWorker(QObject):
while self.isRunning:
print("worker start")
scan_address = self.ip_generator.get_next_address(
*get_converted_arguments(
self.ip_generator.get_next_address,
self.previous_address,
convert_table
)
*convert_for_address_generator_reverse(self.previous_address)
)
if not scan_address:
break
scan_result = self.scanner.scan_address(
*get_converted_arguments(
self.scanner.scan_address,
scan_address,
convert_table
)
*convert_for_scanner(scan_address)
)
print(scan_result)
scan_address.update(scan_result)
self.previous_address = scan_address
self.storage.put_responce(
*get_converted_arguments(
self.storage.put_responce,
scan_address,
convert_table
)
*convert_for_storage(scan_address)
)
string_scan_address = " ".join(key + ":" + str(scan_address[key]) for
key in scan_address.keys())

View File

@ -34,3 +34,28 @@ class ConvertTable():
raise Exception("There is no converter for %s to %s" % (from_keys,
to_key))
return None, None
def get_metaconverter(self, from_keys, to_keys):
'''This function constructs and returns new function used to provide fast
conversion from from_keys to to_keys'''
converters_args = []
converters = []
for key in to_keys:
keys_to_convert, converter = None, None
if key in from_keys:
keys_to_convert = [key]
converter = lambda x : {key: x}
else:
keys_to_convert, converter = self.get_converter(from_keys, key)
converters_args.append(keys_to_convert)
converters.append(converter)
def metaconverter(args_dict):
if args_dict == None:
return [None] * len(converters)
res = []
for i,conv in enumerate(converters):
args = [args_dict[arg] for arg in converters_args[i]]
res.append(*[value for key, value in conv(*args).items()])
return res
return metaconverter

View File

@ -1,7 +1,13 @@
def get_argument_annotations(func):
return list(value for key, value in func.__annotations__.items() if key !=
'return')
def get_return_annotations(func):
return func.__annotations__['return']
def complitable_functions(output_function, input_function, convert_table):
input_keys = set(value for key, value in
input_function.__annotations__.items() if key != 'return')
return_keys = set(output_function.__annotations__["return"])
input_keys = set(get_argument_annotations(input_function))
return_keys = set(get_return_annotations(output_function))
all_possible_return_keys = return_keys.union(
convert_table.all_possible_conversions(return_keys)
)
@ -10,9 +16,13 @@ def complitable_functions(output_function, input_function, convert_table):
return input_keys.issubset(all_possible_return_keys)
def get_converted_arguments(function, simple_arg_dict, convert_table):
#This function returns list of arguments needed by function made from
#simple_arg_dict by convert_table
if simple_arg_dict == None:
return [None for key in function.__annotations__.keys() if key != 'return']
result = []
if not hasattr(function, "__annotations__"):
return result
for key, value in function.__annotations__.items():
if key != 'return':
converted_arg = None
@ -29,4 +39,3 @@ def get_converted_arguments(function, simple_arg_dict, convert_table):
)[value]
result.append(converted_arg)
return result

View File

@ -3,6 +3,8 @@ from core.prototypes.AbstractModuleClass import AbstractModuleClass
class AbstractAddressGenerator(AbstractModuleClass):
'''The class describes addess generation mechanism.'''
INPUT_FUNCTIONS = {"set_parsed_fields", "get_next_address"}
OUTPUT_FUNCTIONS = {"get_next_address", "get_all_addresses"}
@abstractmethod
def set_parsed_fields(self):
'''This method is called after generator initialization. It is used to

View File

@ -1,17 +1,30 @@
def internal(func):
func.is_internal = True
return func
class AbstractModuleClassType(type):
def __new__(self, name, bases, attrs):
print("creating class", name)
base_class = None
if len(bases) != 0:
base_class = bases[0]
input_function_names = None
output_function_names = None
if base_class:
input_function_names = getattr(base_class, "INPUT_FUNCTIONS")
output_function_names = getattr(base_class, "OUTPUT_FUNCTIONS")
else:
input_function_names = attrs["INPUT_FUNCTIONS"]
output_function_names = attrs["OUTPUT_FUNCTIONS"]
if not name.startswith("Abstract"):
for attrname, attrvalue in attrs.items():
if type(attrvalue).__name__ == 'function':
if attrvalue.__name__ not in ["__init__", "save"] and not (hasattr(attrvalue, "is_internal") and attrvalue.is_internal
):
if not name.endswith("Storage"):
if attrvalue.__name__ in input_function_names:
if len(list(filter(lambda x: x!= "return",
attrvalue.__annotations__.keys()))) == 0:
raise Exception(
"%s.%s:no input annotations." %
(name, attrname)
)
if attrvalue.__name__ in output_function_names:
try:
attrvalue.__annotations__["return"]
except KeyError:
@ -19,14 +32,10 @@ class AbstractModuleClassType(type):
"%s.%s: return type is not defined!" %
(name, attrname)
)
if not name.endswith("Parser"):
if not attrvalue.__annotations__:
raise Exception(
"%s.%s: arguments missing annotations!" %
(name, attrname)
)
return super().__new__(self, name, bases, attrs)
class AbstractModuleClass(metaclass = AbstractModuleClassType):
REQUIED_INPUT_KEYS = None
OUTPUT_KEYS = []
INPUT_FUNCTIONS = {}
OUTPUT_FUNCTIONS = {}

View File

@ -4,7 +4,8 @@ from core.prototypes.AbstractModuleClass import AbstractModuleClass
class AbstractParser(AbstractModuleClass):
'''The class describes fields parsing mechanisms'''
INPUT_FUNCTIONS = {}
OUTPUT_FUNCTIONS = {"parse_fields"}
@abstractmethod
def parse_fields(self, args):
'''In address field can be plased any text, describing address of

View File

@ -6,6 +6,8 @@ class AbstractScanner(AbstractModuleClass):
If it can manage many threads by itself set INDEPENDENT_THREAD_MANAGEMENT
to "True"'''
INDEPENDENT_THREAD_MANAGEMENT = False
INPUT_FUNCTIONS = {"scan_address"}
OUTPUT_FUNCTIONS = {"scan_address"}
@abstractmethod
def scan_address(self, address):

43
docs/classes.ru.md Normal file
View File

@ -0,0 +1,43 @@
# Классы-прототипы модулей PySca
Процесс сканирования в PySca разделён на этапы. За каждый этап сканирования отвечает отдельный класс - модуль, реализующий все необходимые для данного этапа функции, декларированнынные в соответствующем классе-прототипе.
Каждая функция модуля PySca - элемент конвеера. На вход она получает результаты выполнения предидущей функции (явно - в виде словаря или же как значения аргументов согласно аннотациям к аргументам) другого модуля и возвращет значение в следующую в цепочке функцию. Связи между функциями можно представить в виде таблицы:
Название функции в абстрактном классе | Функция-источник аргументов | Функция - приёмник результатов
:------------------------------------: | :---------------------------: | :------------------------------:
AbstractParser.parse_fields | вводится пользователем | AbstractAddresGenerator.set_parsed_fields
AbstractAddressGenerator.set_parsed_fields | AbstractParser.parse_fields | нет
AbstractAddressGenerator.get_next_address | AbstractScanner.scan_address + AbstractAddressGenerator.get_next_address или None при первом запросе адреса | AbstractScanner.scan_address и AbstractStorage.put_responce
AbstractScanner.scan_address | AbstractAddressGenerator.get_next_address | AbstractAddressGenerator.get_next_address и AbstractStorage.put_responce
AbstractStorage.put_responce | AbstractAddressGenerator.get_next_address + AbstractScanner.scan_address | нет
AbstractStorage.save | нет | нет
## Описание классов модулей по отдельности
### AbstractParser
Задача этого класса - обработка пользовательского ввода, преобразование строк в именованные python-объекты.
#### Методы AbstractParser
* __init__()
В аргументы передаются запрошенные параметры из config.py
* parse_fields()
В аргументы функции передаётся содержимое текстовых полей, введённое пользователем.
### AbstractAddressGenerator
Задача модулей-наследников этого класса - обработка вывода парсера и генерация адресов - задач для сканирования на основе не только данных парсера, но и результатов, полученных от сканирования предидущих адресов.
#### Методы AbstractAddressGenerator
* __init__()
В аргументы передаются запрошенные параметры из config.py
* set_parsed_fields()
В аргументы получает разультаты AbstractParser.parse_fields. Если нужно обрабатывает их и сохраняет как поля класса.
* get_next_address()
В аргументы получает либо None как значение всех аргументов - для получения первого адреса, либо результаты работы AbstractScanner.scan_address + результаты собственной работы (тот адрес, который сканировал экземпляр AbstractScanner). На основе полученных данных/внутренних полей возвращает адрес для последующего сканирования либо None, если адресов больше нет.
ВАЖНО: Так как обращения к функции класса возможны в асинхронном виде, рекомндуется либо оборачивать код функции в lock класса Threading, либо использоват потокобезопасные структуры как поля класса (Queue и т. п.).
### AbstractScanner
Модули этого класса отвечают за сам процесс сканирования. На данный момент доступно сканирование через функцию только одного адреса, своя реализация параллелизма пока невозможна.
#### Методы AbstractScanner
* __init__()
В аргументы передаются запрошенные параметры из config.py
* scan_address()
На вход метод получает адрес, сгенерированный AbstractAddresGenerator'ом, возвращает результаты сканирования. В процессе сканирования не рекомендуется менять поля класса, а если менять, то только потокобезопасно.
#### Методы AbstractStorage
* __init__()
В аргументы передаются запрошенные параметры из config.py
* put_responce()
На вход получает сумму результатов выполнения AbstractAddressGenerator.get_next_address и AbstractScammer.scan_address и сохраняет их себе в поля/ в реальном времени записывает их в файл.
* save()
Метод вызывается в конце сканирования, когда все потоки скаенра остановлены. Сохраняет информацию в файл.

15
docs/config.ru.md Normal file
View File

@ -0,0 +1,15 @@
# Что такое config.py
config.py - инструмент для выбора модулей цепочки и их предварительной конфигурации.
Синтаксис [config.py](../config.py) выглядит так:
```
config = {
"parser":
{
"name":[ИМЯ_ПАРСЕРА]
"init_args":[словарь с аргументами инициализации]
}
...
}
```
Здесь ИМЯ_ПАРСЕРА - это название файла модуля без расширения и класса в нём, который будет отвечать за парсинг пользовательского ввода.
словарь с аргументами инициализации - все аргументы(кроме self), которые могут понадобиться при инициализации(__init__) модуля этой направленности (не только выбранного, но и любого другого. Может сохраняться при смене модуля, если другой имеет все требуемые аргументы в этом словаре.

View File

@ -0,0 +1,17 @@
# Создание собственного модуля
Каждый модуль PySca - класс, отвечающий за конкретные задачи в цепочке сканирования. Чтобы ваш код стал модулем сканера, необходимо соблюдать следующие условия:
* Модуль должен представлять из себя отдельный файл, содержащий в себе единственный класс, в методах которого и реализована логика модуля. Имя файла модуля должно совпадать с именем содержащегося в нём класса.
* Наследование класса модуля от одного из абстрактных классов AbstractParser, AbstractAddressGenerator, AbstractScanner, AbstractStorage в соответствии с функционалом.
* Расположение файла модуля в общей системе папок PySca зависит от класса-родителя модуля и определяется по этой таблице:
Класс-родитель | Адрес[^pysca_folder_system]
:--------------:|:-----:
AbstractParser | modules/address_generation
AbstractAddressGenerator | modules/address_generation
AbstractScanner | modules/network_scan
AbstractStorage | modules/storage
[^pysca_folder_system]: Все адреса приведены относительно корневого каталога PySca.
* Реализация всех методов класса-родителя с соблюдением условий:
* Аннотация всех аргументов методов (если они необходимы)[^self_argument] ключами требуемых значений.
* Аннотация возвращаемых методами значений в виде множества/списка возвращаемых ключей (если метод должен что-то возвращать).
* Соответствие возвращаемых методом значений аннотации - возвращение словаря с парами ключ-значение, где ключ - элемент из списка заявленных ключей, а значение - соответствующие ему данные, либо возвращение None.
[^self_argument]: Разумеется, self в аннотации не нуждается, более того, аннотация self может сломать сканер.

View File

@ -1,5 +1,4 @@
from core.prototypes.AbstractAddressGenerator import AbstractAddressGenerator
from core.prototypes.AbstractModuleClass import internal
class GDocsAddressGenerator(AbstractAddressGenerator):
def set_parsed_fields(self, prefix:"gdocs_prefix",
@ -13,7 +12,6 @@ class GDocsAddressGenerator(AbstractAddressGenerator):
self.hashlen = len(ranges[0][0])
self.currange = self.ranges.pop(0)
@internal
def hash2int(self, gdhash):
alen = len(self.alphabet)
res = 0
@ -22,7 +20,6 @@ class GDocsAddressGenerator(AbstractAddressGenerator):
res += self.revsymbols[symb]
return res
@internal
def int2hash(self, hint):
alen = len(self.alphabet)
reshash = [self.alphabet[0]]*self.hashlen
@ -31,15 +28,15 @@ class GDocsAddressGenerator(AbstractAddressGenerator):
reshash[i] = self.alphabet[rest]
return "".join(reshash)
def get_next_address(self, prev_url:'url') -> {"url"}:
if not prev_url:
return {'url':self.prefix + self.currange[0]}
prev_hash = prev_url[prev_url.rfind('/') + 1:]
def get_next_address(self, prev_hash:'gdoc_hash') -> {"gdoc_prefix", "gdoc_hash"}:
if not prev_hash:
return {'gdoc_prefix':self.prefix, "gdoc_hash":self.currange[0]}
#prev_hash = prev_url[prev_url.rfind('/') + 1:]
if self.hash2int(self.currange[1]) <= self.hash2int(prev_hash):
if not self.ranges: return None
self.currange = self.ranges.pop(0)
return {'url' : self.prefix + self.currange[0]}
return {'url' : self.prefix + self.int2hash(self.hash2int(prev_hash) +
return {'gdoc_prefix' : self.prefix, 'gdoc_hash':self.currange[0]}
return {'gdoc_prefix' : self.prefix, 'gdoc_hash':self.int2hash(self.hash2int(prev_hash) +
1)}
def get_all_addresses(self) -> {'gdocs_prefix', 'gdocs_hash_ranges'}:

View File

@ -0,0 +1,2 @@
def gdocs_prefix_hash2Url(prefix:'gdoc_prefix', ghash:'gdoc_hash') -> {'url'}:
return {'url':prefix + ghash}

View File

@ -0,0 +1,2 @@
def response2text(response:'response') -> {'text'}:
return {'text': response.text}

View File

@ -2,7 +2,7 @@ import socket
from core.prototypes.AbstractScanner import AbstractScanner
class CoreModel(AbstractScanner):
def __init__(self, timeout):
def __init__(self, timeout:"timeout"):
self.defSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.defSocket.settimeout(int(timeout))

View File

@ -0,0 +1,36 @@
import json
import requests
from urllib.parse import urlencode, urljoin
import lxml.html
from core.prototypes.AbstractScanner import AbstractScanner
class GDocsScanner(AbstractScanner):
def __init__(self, timeout:"timeout"):
pass
def scan_address(self, prefix:"gdoc_prefix", ghash:"gdoc_hash") -> {"response",
"gdoc_info", "gdoc_title"}:
print("Scanning", prefix, ghash)
response = requests.get(prefix+ghash)
if response.status_code != 200:
return {"response":response, "gdoc_info":None, "gdoc_title":None}
print(response.status_code)
response_tree = lxml.html.fromstring(response.text)
(title,) = response_tree.xpath("//meta[@property='og:title']/@content")
(token_container,) = response_tree.xpath('//script[contains(text(),"token")]')
token_container = token_container.text
token_container = token_container[token_container.find("{"):token_container.rfind("}") + 1]
#print(json.dumps(json.loads(token_container), indent=4, sort_keys=True))
try:
info_params = json.loads(token_container)["info_params"]
except json.JSONDecodeError:
return {"response":response, "gdoc_info":None, "gdoc_title":None}
#print(info_params)
info = None
if "token" in info_params.keys():
info_params.update({"id":ghash})
info_url = urljoin(prefix, ghash+"/docdetails/read?"+urlencode(info_params))
print(info_url)
info_text = requests.get(info_url).text
info = json.loads(info_text[info_text.find("\n") + 1:])
print(info)
return {"response":response, "gdoc_info":info,
"gdoc_title":title}

View File

@ -0,0 +1,31 @@
from core.prototypes import AbstractScanner
from urllib.parse import urlencode
import requests
import re
STATS_SEARCHPATTERN = r'<div id="resultStats>([^>]+)'
LINK_SEARCHPATTERN = r'<div class="r"><a href="([^"]+)"'
RESULT_REGEXP = re.compile(LINK_SEARCHPATTERN)
class GoogleSearcher(AbstractScanner):
def __init__(self:
pass
def scan_address(self, query:'google_search_query')->{"search_result_list"}:
search_url = "http://google.com/search?%s"
num_loaded_results = 100
start = 0
search_result_list = set()
while num_loaded_results == 100:
query_params = {
"num":100,
"q":query,
"start":start,
"filter":0
}
page = requests.get(search_url % urlencode(query_params))
if page.status_code != 200:
break
start += 100
result_page = set(RESULT_REGEXP.findall(page_text))
num_loaded_results = len(result_page)

View File

@ -3,7 +3,7 @@ import requests
class URLScanner(AbstractScanner):
def __init__(self, timeout):
def __init__(self):
pass
def scan_address(self, url:"url") -> {"response"}:

View File

@ -1,14 +1,19 @@
from core.prototypes.AbstractStorage import AbstractStorage
import json
class GDocsStorage(AbstractStorage):
def __init__(self, path):
def __init__(self, path:"path"):
self.path = path
self.urls = dict()
def put_responce(self, url:'url', status:'status'):
def put_responce(self, url:'url', status:'status', title:'gdoc_title',
info:'gdoc_info'):
if str(status) not in self.urls.keys():
self.urls[str(status)] = []
self.urls[str(status)].append(url)
self.urls[str(status)] = dict()
print(int(status))
url_object = dict()
if status == 200:
url_object = info
url_object["title"] = title
self.urls[str(status)][url] = url_object
def save(self):
print("saving")
with open(self.path, "w") as f:

View File

@ -4,19 +4,100 @@ from threading import RLock
class JSONStorage(AbstractStorage):
def __init__(self, path):
def __init__(self, path:"path", scheme:"json_scheme"):
self.path = path
self.respdict = dict()
self.scheme = scheme
self.needed_keys = set()
if type(scheme) == dict:
left_nodes = []
self.needed_keys = self.needed_keys.union(set(
filter(JSONStorage.is_needed_key,scheme.keys())))
left_nodes.extend(scheme.values())
for node in left_nodes:
if type(node) == str and JSONStorage.is_needed_key(node):
self.needed_keys.add(node)
elif type(node) == set or type(node) == list:
left_nodes.extend(list(node))
#self.needed_keys = self.needed_keys.union(set(
#filter(JSONStorage.is_needed_key, node)))
elif type(node) == dict:
self.needed_keys = self.needed_keys.union(
set(filter(JSONStorage.is_needed_key,
node.keys())))
left_nodes.extend(node.values())
elif type(scheme) == set or type(scheme) == list:
self.needed_keys = set(
filter(JSONStorage.is_needed_key, scheme.copy()))
elif type(scheme) == str and JSONStorage.is_needed_key(scheme):
self.needed_keys.add(scheme)
self.respdict = type(scheme)()
self.needed_keys = list(self.needed_keys)
setattr(self.put_responce.__func__, "__annotations__", {str(i):arg for
i, arg in enumerate(self.needed_keys)})
self.lock = RLock()
def put_responce(self, ip:'ipv4_str', port:'port', scan_result:'scan_result'):
'''def put_responce(self, ip:'ipv4_str', port:'port', scan_result:'scan_result'):
if ip not in self.respdict.keys():
self.respdict[ip] = {"open": [], "close": []}
self.respdict[ip]["open" if scan_result == 0
else "close"].append(port)
else "close"].append(port)'''
#Все ключи, начинающиеся с "@", считаются значениями и не декодируются.
@staticmethod
def is_needed_key(string):
return not string.startswith("@")
@staticmethod
def get_element_name(key, named_args):
return key[1:] if not JSONStorage.is_needed_key(key) else named_args[key]
@staticmethod
def get_node_adder(node, key = None):
adder = None
if key == None:
adder = node.append if type(node) == list else node.add
elif type(key) == str:
def result(x):
node[key] = x
adder = result
return adder
@staticmethod
def process_scheme(scheme, current_level, named_args):
print("processing scheme", scheme)
if type(scheme) == str:
JSONStorage.get_node_adder(current_level)(JSONStorage.get_element_name(scheme, named_args))
elif type(scheme) == set or type(scheme) == list:
for el in scheme:
if type(el) == str:
JSONStorage.process_scheme(el, current_level, named_args)
elif type(el) == dict:
current_level.append(dict())
JSONStorage.process_scheme(el, current_level[-1], named_args)
elif type(scheme) == dict:
for key, value in scheme.items():
reversed_key = JSONStorage.get_element_name(key, named_args)
if type(value) == str:
print(value, named_args)
JSONStorage.get_node_adder(current_level,
reversed_key)(JSONStorage.get_element_name(value,
named_args))
else:
if reversed_key not in current_level:
current_level[reversed_key] = type(value)()
JSONStorage.process_scheme(
value,
current_level[reversed_key],
named_args
)
def put_responce(self, *args) -> {"A"}:
named_args = {self.needed_keys[i]:arg for i, arg in
enumerate(list(args))}
with self.lock:
JSONStorage.process_scheme(self.scheme, self.respdict, named_args)
def save(self):
with self.lock:
print("saving")
print(self.respdict)
with open(self.path, "w") as f:
json.dump(self.respdict, f)
json.dump(self.respdict, f, default = lambda o: o if not
isinstance(o, set) else list(o))
self.respdict = {}

1
results.json Normal file
View File

@ -0,0 +1 @@
{"200": {"https://docs.google.com/document/d/": [{"hash": "11Sz_PyqL268V9xmcEjYqEhufFGleT5TowdKEu5cTFak", "title": null}, {"hash": "11Sz_PyqL268V9xmcEjYqEhufFGleT5TowdKEu5cTFal", "title": null}, {"hash": "11Sz_PyqL268V9xmcEjYqEhufFGleT5TowdKEu5cTFam", "title": null}]}}