Commit 98078998 authored by yogesh.m's avatar yogesh.m

update

parents

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

# Default ignored files
/shelf/
/workspace.xml
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (security-management (offline))" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/security-management (offline).iml" filepath="$PROJECT_DIR$/.idea/security-management (offline).iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
Steps to do after cloning the code into sensor:<br>
1)Open config.yml and change the `unifytwin_server_ip_address - Your IP address of the server where the assets data has to be transferred`<br>
2)run `sudo ./install.sh` (assets will auomatically be sent to the servers ip address specified in step1)<br>
<br>
To stop all the services:<br>
1)run `sudo ./uninstall`<br>
\ No newline at end of file
import sqlite3
import threading
import requests
import yaml
import os
import json
from time import sleep
from sys import platform
yamlfile=open("config.yaml")
data = yaml.load(yamlfile, Loader=yaml.FullLoader)
plant=data["configuration"]["edge_device_location"]
lock = threading.Lock()
server_ip_address=data["configuration"]["unifytwin_server_ip_address"]
def transmit_all_assets():
dicc={}
retry=0
try:
if platform == "linux" or platform == "linux2":
os.system(
'sqlite3 cloned.db "DROP TABLE IF EXISTS inventory" |sqlite3 assets.db ".dump inventory"|sqlite3 cloned.db')
elif platform == "darwin":
os.system(
'sqlite3 cloned.db "DROP TABLE IF EXISTS inventory" |sqlite3 assets.db ".dump inventory"|sqlite3 cloned.db')
elif platform == "win32":
os.system('del cloned.db && copy assets.db cloned.db')
mydb=sqlite3.connect("cloned.db",check_same_thread=False)
query=mydb.cursor()
lock.acquire()
lock.release()
get_asset='SELECT * FROM inventory;'
asset_list=query.execute(get_asset).fetchall()
query.close()
mydb.commit()
mydb.close()
#requests.get(server_ip_address, data=str(asset_list))
for i in asset_list:
dicc[i[0]]={}
dicc[i[0]]["name"]=i[1]
dicc[i[0]]["last_activity"]=i[2]
dicc[i[0]]["type"]=i[3]
dicc[i[0]]["protocols"]=i[4]
dicc[i[0]]["mac_address"]=i[5]
dicc[i[0]]["vendor"]=i[6]
dicc[i[0]]["firmware_version"]=i[7]
dicc[i[0]]["model"]=i[8]
dicc[i[0]]["operating system"]=i[9]
dicc[i[0]]["plant"]=i[10]
return json.dumps(dicc)
except Exception as e:
pass
def periodic_transmit_all_assets():
while True:
sleep(7)
retry=0
try:
if platform == "linux" or platform == "linux2":
os.system('sqlite3 cloned.db "DROP TABLE IF EXISTS inventory" |sqlite3 assets.db ".dump inventory"|sqlite3 cloned.db')
elif platform == "darwin":
os.system('sqlite3 cloned.db "DROP TABLE IF EXISTS inventory" |sqlite3 assets.db ".dump inventory"|sqlite3 cloned.db')
elif platform == "win32":
os.system('del cloned.db && copy assets.db cloned.db')
mydb=sqlite3.connect("cloned.db",check_same_thread=False)
query=mydb.cursor()
lock.acquire()
lock.release()
get_asset='SELECT * FROM inventory;'
asset_list=query.execute(get_asset).fetchall()
query.close()
mydb.commit()
mydb.close()
requests.get(server_ip_address, data=str(asset_list))
except Exception as e:
print(e)
retry=retry+1
if(retry<3):
periodic_transmit_all_assets()
else:
requests.get(server_ip_address, data=str(e))
def delete_all_assets():
try:
mydb = sqlite3.connect("assets.db", check_same_thread=False)
query = mydb.cursor()
statement='DELETE FROM inventory;'
lock.acquire()
lock.release()
query.execute(statement)
query.close()
mydb.commit()
mydb.close()
except:
delete_all_assets()
return
from sniff import start_sniff
import sys
import getopt
from utilities import list_interfaces as li
import asset_actions
global k_
k_ = {
'start': None,
'iface': None
}
def help(_exit_=False):
print("Usage: %s [OPTION]\n" % sys.argv[0])
print("\t--start\tStart sniff")
print("\t-i\tSelect Interface")
print("\t--list_interfaces\tSelect Interface")
print("\t--transmit_assets\tTransmit all assets to Configured Server")
if _exit_:
sys.exit()
def main():
global k_
if len(sys.argv) < 2:
help(1)
try:
opts, args = getopt.getopt(sys.argv[1:],"s:i:l:t:h:",["start","iface=","list_interfaces","transmit_assets","help"])
except Exception as e:
print(e)
for o, a in opts:
if o == '--start':
k_['start'] = 1
if o == '-i':
k_['iface'] = a
if o == '--list_interfaces':
li.show_list()
if o == '--transmit_assets':
asset_actions.transmit_all_assets()
if o == '--help':
help(1)
if(k_['start']):
if(k_['iface']):
start_sniff(k_['iface'])
else:
help(1)
if __name__ == '__main__':
main()
\ No newline at end of file
File added
File added
configuration:
platform: OT
asset_discovery: False #To enable disable the scan
threads: 20 #number of threads to scan at once
discovery frequency: 1800 minutes #how frequent the scan needs to be
scan_protocol: ethernetip_enum,sip_enum #types of protocol to use
ot_ports : [80,102,443,502,530,593,789,1089,1090,1091,1911,1962,2222,2404,4000,4840,4843,4911,9600,19999,20000,20547,34962,34963,34964,34980,44818,46823,46824,55000,55001,55002,55003] #adding all OT ports to check
packet_storage: local #where to store packets local/remote(server)
unifytwin_server_ip_address: https://webhook.site/d3338504-e5f0-4c2b-ad21-ae7923119f16 #Data will be sent back here
asset_range_start: 10.10.3.214 #where to start the scan from
asset _range_end: 10.10.3.225 #where to end the scan from
edge_device_location : Dalmia Cement
port_monitoring: True #On/Off
pcap_analysis: False
null_loopback: False
interfaces: Wi-Fi
blacklist_ip: ['46.4.105.116','172.67.214.157','3.6.115.64','104.21.53.154']
blacklist_dns: ['webhook.site.','hpd.gasmi.net.','dd01-14-98-12-178.in.ngrok.io.']
import binascii
import json
class Packet_Analyzer():
def __init__(self):
data=None
def identify_protocol(self,hex_pkt):
protocols=""
if(b'6300' in hex_pkt and hex_pkt[84:88]==b'6300'):
protocols=protocols+"enip:"
if(b'0300' in hex_pkt and hex_pkt[108:112]==b'0300'):
protocols=protocols+"s7comm:"
if(hex_pkt[46:48] == b"06"):
protocols=protocols+"tcp"
if (hex_pkt[46:48] == b"11"):
protocols = protocols + "udp"
return protocols
def get_ip(self,hex_pkt):
if(hex_pkt[24:28]==b"0800"):
ip_inhex= hex_pkt[28+24:28+24+8]
ip = str(int.from_bytes(binascii.unhexlify(ip_inhex[0:2]), byteorder="little"))+"."+str(int.from_bytes(binascii.unhexlify(ip_inhex[2:4]), byteorder="little"))+"."+str(int.from_bytes(binascii.unhexlify(ip_inhex[4:6]), byteorder="little"))+"."+str(int.from_bytes(binascii.unhexlify(ip_inhex[6:8]), byteorder="little"))
return ip
def get_mac(self,hex_pkt):
souce_mac = hex_pkt[12:24].decode()
modified_src_mac = ":".join(souce_mac[i:i + 2] for i in range(0, len(souce_mac), 2))
return modified_src_mac
def get_tcp_port(self,inhex):
if(inhex[46:48]==b"06"):
port = int(inhex[68:72], 16)
return port
def get_udp_port(self,inhex):
if (inhex[46:48] == b"11"):
port = int(inhex[68:72], 16)
return port
def find_mac(self,mac, trim_count=-1):
try:
if len(mac[:trim_count]) == 0:
return "No Mac"
else:
res = self.data[mac[:trim_count]]
return res
except:
r = self.find_mac(mac, trim_count - 1)
return r
def get_vendor(self,mac):
f = open("utilities/mac_vendors.json")
self.data = json.loads(f.read())
vendor = self.find_mac(mac.upper())
return vendor
\ No newline at end of file
This diff is collapsed.
sudo cp ../security-management /usr/share -r
test -f /usr/share/security-management/config.yaml && ifaces=$(cat /usr/share/security-management/config.yaml | grep interfaces| sed 's/interfaces: //g') ||ifaces=$(ip link show | grep ': '|sed 's/\: <.*//g'| sed 's/[0-9]: //g')
inface=$(echo $ifaces|sed 's/ .*//g')
echo '[Unit]
Description=asset_discovery service
After=syslog.target network-online.target
[Service]
Type=simple
Restart=on-failure
WorkingDirectory=/usr/share/security-management/
User=root
ExecStart=/bin/bash /usr/share/security-management/start.sh --start -i '$inface'
ExecStop=/bin/bash /usr/share/security-management/stop.sh
[Install]
WantedBy=multi-user.target' |sudo tee /etc/systemd/system/security-management.service >/dev/null
sudo systemctl daemon-reload
sudo ln -s /usr/share/security-management/start.sh /bin/security-management
(sudo crontab -l ; echo "@reboot /bin/security-management --start -i $inface >> /var/log/assets_management_logs.txt 2&1")| sudo crontab -
sudo service security-management start
echo 'Install Complete!!!'
echo '###################################################################
#Start Asset Discovery : sudo service security-management start
#Interface currently used : '$inface'
###################################################################'
from protocol_enumerators import ethernetip_enum as eip
from protocol_enumerators import s7_enum as s7
from protocol_enumerators import bacnet as bac
from protocol_enumerators import modbus
from helpers.port_service_helper import psdata
from protocol_enumerators import omron
import binascii
import os
from helpers.Packet_Analyzer import Packet_Analyzer
def analyse_protocol(protocols,pkt):
dev_type="Unknown"
vendor="Unknown"
firmware="Unknown"
model="Unknown"
inhex = binascii.hexlify(bytes(pkt))
pa=Packet_Analyzer()
if("enip" in protocols):
print(protocols)
res=eip.get_info(pa.get_ip(inhex),int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex)))
if(res):
dev_type=res['Type']
vendor=res['Vendor']
firmware=res['ProductName']
model=res['SerialNumber']
elif("s7comm" in protocols):
res=s7.get_info(eip.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex))))
if(res):
dev_type=res['Module Type']+" "+res['System Name']
vendor=res['Copyright']
firmware=res['Module']+" "+res['Version']
model=res['Serial Number']
elif("bacnet" in protocols):
res=bac.get_info(eip.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex))))
if(res):
dev_type=res['desc']
vendor=res['vendorid']
firmware=res['firmware']
model=res['model']
elif("modbus" in protocols):
res=modbus.get_info(eip.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex))),False)
if(res):
dev_type=res['Device identification']
vendor=res['Slave ID data']
model=res['sid']
elif("omron" in protocols):
res=omron.get_info(eip.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex))),False)
if(res):
dev_type='Omron Device'
vendor='Omron Devices'
firmware=res['Controller Version']
model=res['Controller Model']
else:
dev_type=psdata[str(pa.get_tcp_port(inhex))] if "tcp" in protocols else psdata[str(pa.get_udp_port(inhex))] if "udp" in protocols else "Unknown"
vendor=pa.get_vendor(pa.get_mac(inhex))
return dev_type,vendor,firmware,model
def update_protocol(protocols,pkt):
dev_type="Unknown"
vendor="Unknown"
firmware="Unknown"
model="Unknown"
inhex = binascii.hexlify(bytes(pkt))
pa=Packet_Analyzer()
if("enip" in protocols):
res=eip.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex)))
if(res):
dev_type=res['Type']
vendor=res['Vendor']
firmware=res['ProductName']
model=res['SerialNumber']
elif("s7comm" in protocols):
res=s7.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex)))
if(res):
dev_type=res['Module Type']+" "+res['System Name']
vendor=res['Copyright']
firmware=res['Module']+" "+res['Version']
model=res['Serial Number']
elif("bacnet" in protocols):
res=bac.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex)))
if(res):
dev_type=res['desc']
vendor=res['vendorid']
firmware=res['firmware']
model=res['model']
elif("modbus" in protocols):
res=modbus.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex)),False)
if(res):
dev_type=res['Device identification']
vendor=res['Slave ID data']
model=res['sid']
elif("omron" in protocols):
res=omron.get_info(pa.get_ip(inhex) ,int(pa.get_tcp_port(inhex)) if "tcp" in protocols else int(pa.get_udp_port(inhex)),False)
if(res):
dev_type='Omron Device'
vendor='Omron Devices'
firmware=res['Controller Version']
model=res['Controller Model']
return dev_type,vendor,firmware,model
\ No newline at end of file
This diff is collapsed.
import binascii
from scapy.all import *
import socket
def action(host,port):
output={}
cotp=binascii.unhexlify('0300001611e00000001400c1020100c2020102c0010a')
alt_COTP = binascii.unhexlify("0300001611e00000000500c1020100c2020200c0010a")
ROSCTR_Setup = binascii.unhexlify("0300001902f08032010000000000080000f0000001000101e0")
Read_SZL = binascii.unhexlify("0300002102f080320700000000000800080001120411440100ff09000400110001")
first_SZL_Request = binascii.unhexlify("0300002102f080320700000000000800080001120411440100ff09000400110001")
second_SZL_Request = binascii.unhexlify("0300002102f080320700000000000800080001120411440100ff090004001c0001")
response=None
pkt = Ether(cotp)
MESSAGE = pkt
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.settimeout(3)
s.connect((host,port))
except:
return False
response=send_receive(s,cotp)
if(response):
if(hex(response[5])!="0xd0"):
s.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
response=send_receive(s,alt_COTP)
if(response):
if(hex(response[5])!="0xd0"):
return False
response = send_receive(s,ROSCTR_Setup)
if(response):
if(hex(response[7])!="0x32"):
return False
response = send_receive(s,Read_SZL)
if(response):
if(hex(response[7])!="0x32"):
return False
response = send_receive(s, first_SZL_Request)
try:
output = first_parse_response(response,output)
except:
return False
response = send_receive(s, second_SZL_Request)
output=second_parse_response(response,output)
output["DeviceIP"]=host
output["Port"]=port
return output
\ No newline at end of file
This diff is collapsed.
import struct
from scapy.all import *
import socket
import json
output={}
modbus_exception_codes = {
1 : 'ILLEGAL FUNCTION',
2 : 'ILLEGAL DATA ADDRESS',
3 : 'ILLEGAL DATA VALUE',
4 : 'SLAVE DEVICE FAILURE',
5 : 'ACKNOWLEDGE',
6 : 'SLAVE DEVICE BUSY',
8 : 'MEMORY PARITY ERROR',
10 : 'GATEWAY PATH UNAVAILABLE',
11 : 'GATEWAY TARGET DEVICE FAILED TO RESPOND'
}
def form_rsid(sid, functionId, data):
payload_len = 2
if(len(data)>0):
payload_len = payload_len+len(data)
return b"\0\0\0\0\0"+struct.pack('BBB', payload_len, sid, functionId)+data
def discover_device_id_recursive(host, port, sid, start_id, objects_table):
rsid = form_rsid(sid, 0x2B, b"\x0E\x01"+struct.pack('B',start_id))
result = comm(host, port, rsid)
object_value=None
if (result!=False and len(result) >= 8):
ret_code = result[7]
if ( ret_code == 43 and len(result) >= 15 ):
more_follows = result[11]
next_object_id =result[12]
number_of_objects =result[13]
offset = 15
for i in range(start_id,(number_of_objects-2)):
object_len = result[offset+1]
if object_len == None:
break
object_value = result[offset + 1:offset + object_len]
offset = offset + 2 + object_len
if ( more_follows == 255 and next_object_id != 0 ):
return discover_device_id_recursive(host, port, sid, next_object_id, objects_table)
return object_value
def discover_device_id(host, port, sid):
return discover_device_id_recursive(host, port, sid, 0x0, {})
def extract_slave_id(response):
try:
byte_count = response[8]
if( byte_count == None or byte_count == 0):
return None
return struct.unpack(str(byte_count)+"s",response[9:-1])[0].decode()
except:
return None
def comm(host, port, rsid):
BUFFER_SIZE = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.settimeout(3)
s.connect((host,port))
s.send(rsid)
data = s.recv(BUFFER_SIZE)
return data
except ConnectionError:
return False
except:
return 'terr'
def action(host,port,aggressive):
count=0
for sid in range(1,247):
rsid=form_rsid(sid, 0x11, b"")
result=comm(host, port, rsid)
if(result!=False and len(result)>8):
output[sid]={'Slave ID data':'Unknown','Device identification':'Unknown'}
if(result[7]==17 or result[7]==145):
if(result[7]==17):
slave_id = extract_slave_id(result)
output[sid]["Slave ID data"] = slave_id if slave_id else "Unknown"
elif(result[7]==145):
exception_code = result[8]
exception_string = modbus_exception_codes[exception_code] if exception_code<12 and exception_code>0 else None
if(exception_string==None):
exception_string = "Unknown exception, Code="+str(exception_code)
output[sid]["Error"]=exception_string
else:
return False
device_table = discover_device_id(host, port, sid)
if (device_table!=None and len(device_table) > 0 ):
output[sid]["Device identification"] = re.sub('[\x00-\x1f]',' ',device_table.decode()).replace(" "," ")
count=0
if not aggressive:
output[sid]["sid"]="sid"+str(sid)
return output[sid]
elif(result=='terr' and count>2):
return False
elif(result==False):
return False
else:
count=count+1
return json.dumps(output)
def get_info(ip,port,aggressive=False):
return(action(ip,port,aggressive))
\ No newline at end of file
import modbus
import json
import sqlite3
def discover_modbus_slaves(host,port):
mydb=sqlite3.connect("../assets.db",check_same_thread=False)
query=mydb.cursor()
modbus_data=modbus.get_info(host,port,True)
slave_data=json.loads(modbus_data) if modbus_data!=False else False
if(slave_data):
for slave_key in slave_data:
insinventory='INSERT INTO modbus_devices\
(sid,sid_data,device_info,ip_address)\
VALUES\
("'+slave_key+'","'+slave_data[slave_key]["Slave ID data"]+'","'+slave_data[slave_key]["Device identification"]+'","103.94.108.252")'
query.execute(insinventory)
query.close()
mydb.commit()
\ No newline at end of file
import binascii
from scapy.all import *
import socket
memcard = {
0 : "No Memory Card",
1 : "SPRAM",
2 : "EPROM",
3 : "EEPROM"
}
def memory_card(value):
mem_card = memcard[value] if value in memcard else "Unknown Memory Card Type"
return mem_card
def send_tcp(s):
req_addr = binascii.unhexlify("46494e530000000c000000000000000000000000")
controller_data_read = binascii.unhexlify("46494e5300000015000000020000000080000200")
controller_data_read2 = binascii.unhexlify("000000ef050501")
BUFFER_SIZE = 1024
try:
s.send(req_addr)
response = s.recv(BUFFER_SIZE)
except:
return False
header = response[0]
if(header == 70):
address = response[:23]
controller_data = controller_data_read+address+controller_data_read2+binascii.unhexlify('00')
try:
s.send(controller_data)
res = s.recv(BUFFER_SIZE)
except:
return False
return res
return "ERROR"
def send_udp(host,port):
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
BUFFER_SIZE = 1024
controller_data_read = binascii.unhexlify("800002000000006300ef050100")
try:
s.sendto(controller_data_read,(host,port))
response = s.recv(BUFFER_SIZE)
except:
return False
return response
def action(host,port,protocol):
output={}
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.settimeout(3)
s.connect((host,port))
except:
return False
response = ""
offset = 0
if (protocol == "tcp"):
offset = 16
response = send_tcp(s)
else:
response = send_udp(host,port)
header = response[0]
if(header==192 or header==193 or header==70):
response_code = response[12+offset]
if(response_code == 2081):
output["Response Code"] = "Data cannot be changed (0x2108)"
elif(response_code == 290):
output["Response Code"] = "The mode is wrong (executing) (0x2201)"
elif(response_code == 0):
output["Response Code"] = "Normal completion (0x0000)"
output["Controller Model"] = ((response[14+offset:].split(b"\x00",1))[0]).decode()
output["Controller Version"] = ((response[34+offset:].split(b"\x00",1))[0]).decode()
output["For System Use"] = ((response[54+offset:].split(b"\x00",1))[0]).decode()
output["Program Area Size"] = str(response[95])
output["IOM size"] = str(response[96])
output["No. DM Words"] = str(int(binascii.hexlify(response[97:99]),base=16))
output["Timer/Counter"] = str(response[99])
output["Expansion DM Size"] = str(response[100])
output["No. of steps/transitions"] = str(int(binascii.hexlify(response[101:102]),base=16))
mem_card_type = response[102]
output["Kind of Memory Card"] = memory_card(mem_card_type)
output["Memory Card Size"] = str(int(binascii.hexlify(response[103:104]),base=16))
return (output)
def get_info(ip,port,protocol='udp'):
return(action(ip,port,protocol))
\ No newline at end of file
import binascii
from scapy.all import *
import socket
def send_receive(s,pack):
BUFFER_SIZE = 1024
try:
s.send(pack)
data = s.recv(BUFFER_SIZE)
except:
return False
return data
def first_parse_response(response,output):
value=hex(response[7])
if (value == "0x32"):
output["Module"]=response[43:43+19].decode()
output["Basic_Hardware"]=response[71:71+19].decode()
output["Version"]="%s.%s.%s"%(response[122],response[122+1],response[122+2])
return output
else:
return False
def second_parse_response(response,output):
value=hex(response[7])
offset = 0
szl_id = hex(response[30])
if (value == "0x32"):
if( szl_id != "0x1c" ):
offset = 4
output["System Name"]=((response[39+offset:].split(b"\x00",1))[0]).decode()
output["Module Type"] =((response[73+offset:].split(b"\x00",1))[0]).decode()
output["Serial Number"]=((response[175+offset:].split(b"\x00",1))[0]).decode()
output["Copyright"]=((response[141+offset:].split(b"\x00",1))[0]).decode()
return output
def action(host,port):
output={}
cotp=binascii.unhexlify('0300001611e00000001400c1020100c2020102c0010a')
alt_COTP = binascii.unhexlify("0300001611e00000000500c1020100c2020200c0010a")
ROSCTR_Setup = binascii.unhexlify("0300001902f08032010000000000080000f0000001000101e0")
Read_SZL = binascii.unhexlify("0300002102f080320700000000000800080001120411440100ff09000400110001")
first_SZL_Request = binascii.unhexlify("0300002102f080320700000000000800080001120411440100ff09000400110001")
second_SZL_Request = binascii.unhexlify("0300002102f080320700000000000800080001120411440100ff090004001c0001")
response=None
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.settimeout(3)
s.connect((host,port))
except:
return False
response=send_receive(s,cotp)
if(response):
if(hex(response[5])!="0xd0"):
s.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
response=send_receive(s,alt_COTP)
if(response):
if(hex(response[5])!="0xd0"):
return False
response = send_receive(s,ROSCTR_Setup)
if(response):
if(hex(response[7])!="0x32"):
return False
response = send_receive(s,Read_SZL)
if(response):
if(hex(response[7])!="0x32"):
return False
response = send_receive(s, first_SZL_Request)
try:
output = first_parse_response(response,output)
except:
return False
response = send_receive(s, second_SZL_Request)
output=second_parse_response(response,output)
output["DeviceIP"]=host
output["Port"]=port
return output
def get_info(ip,port):
return(action(ip,port))
\ No newline at end of file
scapy==2.4.5
mac-vendor-lookup==0.1.12
PyYAML==6.0
flask==2.2.2
requests==2.28.2
\ No newline at end of file
from protocol_enumerators import ethernetip_enum
from protocol_enumerators import s7_enum
from protocol_enumerators import bacnet
from protocol_enumerators import modbus
from protocol_enumerators import omron
import sqlite3
from datetime import datetime
mydb=sqlite3.connect("assets.db",check_same_thread=False)
probes=[s7_enum,ethernetip_enum,bacnet,modbus,omron]
def map_ports(ip,port):
query=mydb.cursor()
dev_type="Unknown"
vendor="Unknown"
firmware="Unknown"
model="Unknown"
for i in probes:
res=i.get_info(ip,port)
if(res):
if(i.__name__=="protocol_enumerators.s7_enum"):
dev_type=res['Module Type']+" "+res['System Name']
vendor=res['Copyright']
firmware=res['Module']+" "+res['Version']
model=res['Serial Number']
updateproto='UPDATE inventory SET protocols=(select protocols from inventory where ip_address="'+ip+'")||":"||"s7comm",last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'",type="'+dev_type+'",vendor="'+vendor+'",firmware_version="'+firmware+'",model="'+model+'" WHERE ip_address="'+ip+'"'
query.execute(updateproto)
mydb.commit()
query.close()
elif(i.__name__=="protocol_enumerators.ethernetip_enum"):
dev_type=res['Type']
vendor=res['Vendor']
firmware=res['ProductName']
model=res['SerialNumber']
updateproto='UPDATE inventory SET protocols=(select protocols from inventory where ip_address="'+ip+'")||":"||"enip",last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'",type="'+dev_type+'",vendor="'+vendor+'",firmware_version="'+firmware+'",model="'+model+'" WHERE ip_address="'+ip+'"'
query.execute(updateproto)
mydb.commit()
query.close()
elif(i.__name__=="protocol_enumerators.bacnet"):
dev_type=res['desc']
vendor=res['vendorid']
firmware=res['firmware']
model=res['model']
updateproto='UPDATE inventory SET protocols=(select protocols from inventory where ip_address="'+ip+'")||":"||"bacnet",last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'",type="'+dev_type+'",vendor="'+vendor+'",firmware_version="'+firmware+'",model="'+model+'" WHERE ip_address="'+ip+'"'
query.execute(updateproto)
mydb.commit()
query.close()
elif(i.__name__=="protocol_enumerators.modbus"):
dev_type=res['Device identification']
vendor=res['Slave ID data']
model=res['sid']
updateproto='UPDATE inventory SET protocols=(select protocols from inventory where ip_address="'+ip+'")||":"||"modbus",last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'",type="'+dev_type+'",vendor="'+vendor+'",firmware_version="'+firmware+'",model="'+model+'" WHERE ip_address="'+ip+'"'
query.execute(updateproto)
mydb.commit()
query.close()
elif(i.__name__=="protocol_enumerators.omron"):
dev_type='Omron Device'
vendor='Omron Devices'
firmware=res['Controller Version']
model=res['Controller Model']
updateproto='UPDATE inventory SET protocols=(select protocols from inventory where ip_address="'+ip+'")||":"||"modbus",last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'",type="'+dev_type+'",vendor="'+vendor+'",firmware_version="'+firmware+'",model="'+model+'" WHERE ip_address="'+ip+'"'
query.execute(updateproto)
mydb.commit()
query.close()
break
map_ports('87.59.100.251',9600)
\ No newline at end of file
from flask import Flask, request, render_template
import asset_actions
import json
import os
import sniff
import multiprocessing
def get_interfaces():
interfaces = []
output = os.popen('route print').read()
lines = output.split('\n')
for line in lines:
if ("......" in line):
interfaces.append(line.split("......")[1])
return interfaces
app = Flask(__name__, template_folder='template')
status="Offline"
@app.route('/')
# ‘/’ URL is bound with hello_world() function.
def hello_world():
global status
headings = ["name", "last_activity", "type", "protocols", "mac_addr", "vendor", "firmware_version", "model", "os",
"plant"]
try:
data = json.loads(asset_actions.transmit_all_assets())
except:
data = {}
return render_template("index.html", headings=headings, data=data, interfaces=get_interfaces(),status=status)
process=None
@app.route('/start_sniff',methods = ['POST', 'GET'])
def start_sniff():
global process
global status
interface = request.form.get('interface')
process = multiprocessing.Process(target=sniff.start_sniff, args=(interface,))
if(process.is_alive()):
process.kill()
process.start()
else:
process.start()
status="listening on "+interface+""
return hello_world()
@app.route('/stop_sniff',methods = ['POST', 'GET'])
def stop_sniff():
global process
global status
if(process):
if(process.is_alive()):
process.kill()
status="Offline"
return hello_world()
@app.route('/refresh',methods = ['POST', 'GET'])
def refresh():
# headings = ["name", "last_activity", "type", "protocols", "mac_addr", "vendor", "firmware_version", "model", "os",
# "plant"]
# try:
# data = json.loads(asset_actions.transmit_all_assets())
# except:
# data = {}
# return render_template("index.html", headings=headings, data=data, interfaces=get_interfaces())
return hello_world()
@app.route('/delete_devices',methods = ['POST', 'GET'])
def delete():
asset_actions.delete_all_assets()
return hello_world()
# main driver function
if __name__ == '__main__':
app.run(debug=True, port=8080)
from scapy.all import *
import socket
import datetime
import os
import ssl
import binascii
import yaml
import sqlite3
import xml.etree.ElementTree as ET
from datetime import datetime
import protocol_actions
import asset_actions
import threading
import requests
lock = threading.Lock()
from helpers.Packet_Analyzer import Packet_Analyzer
os.chdir('.')
mydb=sqlite3.connect("assets.db",check_same_thread=False)
try:
yamlfile=open("config.yaml")
data = yaml.load(yamlfile, Loader=yaml.FullLoader)
server_ip=data["configuration"]["unifytwin_server_ip_address"]
packet_storage=data["configuration"]["packet_storage"]
plant=data["configuration"]["edge_device_location"]
balacklist_ips=data["configuration"]["blacklist_ip"]
blacklist_dns=data["configuration"]["blacklist_dns"]
configured_threads=data["configuration"]["threads"]
interfaces=data["configuration"]["interfaces"]
null_loopback=data["configuration"]["null_loopback"]
except Exception as e:
server_ip=""
packet_storage="local"
plant="Unknown Location"
balacklist_ips=['46.4.105.116','172.67.214.157','3.6.115.64','104.21.53.154']
blacklist_dns=['webhook.site.','hpd.gasmi.net.','dd01-14-98-12-178.in.ngrok.io.']
configured_threads=10
interfaces='eth0'
null_loopback=False
from sys import platform
if platform == "linux" or platform == "linux2":
clearing='clear'
elif platform == "darwin":
clearing='clear'
elif platform == "win32":
clearing='cls'
def convert_text(pkt):
query=mydb.cursor()
lock.acquire()
lock.release()
inhex=binascii.hexlify(bytes(pkt))
if(null_loopback):
inhex=b'0000000000000000000000000800'+inhex[8:] if inhex[:2]==b'02' else b'00000000000000000000000086DD'+inhex[8:]
try:
pa = Packet_Analyzer()
protocols=pa.identify_protocol(inhex)
ip=pa.get_ip(inhex)
if(ip):
mac=pa.get_mac(inhex)
check_exist=query.execute("SELECT ip_address FROM inventory WHERE ip_address='"+ip+"'").fetchone()
if(not check_exist):
# add_subnet= True if (ip in str(os.popen('ip addr |grep inet| grep -v "::"| sed "s/inet //g"| sed -e "s/\/.*//g"').read())) else os.system("echo "+ip+" | grep -qa '^10.\|^192\.168\|^172\.16\|^2.2.' && sudo ip address add "+ip+".100/24 dev "+interfaces)
dev_type,vendor,firmware,model=protocol_actions.analyse_protocol(protocols,pkt)
insinventory='INSERT INTO inventory\
(ip_address,name,last_activity,type,protocols,mac_address,vendor,firmware_version,model,operating_system,plant)\
VALUES\
("'+ip+'","'+ip+'","'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'","'+dev_type+'","'+str(protocols)+'","'+mac+'","'+vendor+'","'+firmware+'","'+model+'","Unknown","'+plant+'")'
query.execute(insinventory)
query.close()
mydb.commit()
else:
prev_protocols=query.execute("SELECT protocols FROM inventory WHERE ip_address='"+ip+"'").fetchone()[0]
extra_proto=set(protocols.split(':'))-set(prev_protocols.split(':'))
if(extra_proto):
# add_subnet= True if (ip in str(os.popen('ip addr |grep inet| grep -v "::"| sed "s/inet //g"| sed -e "s/\/.*//g"').read())) else os.system("echo "+ip+" | grep -qa '^10.\|^192\.168\|^172\.16\|^2.2.' && sudo ip address add "+ip+".100/24 dev "+interfaces)
dev_type,vendor,firmware,model=protocol_actions.update_protocol(protocols,pkt)
prev_protocols=prev_protocols+':'+str(extra_proto).replace(", ",":").replace("{","").replace("}","").replace("'","")
if(dev_type!="Unknown"):
updateproto='UPDATE inventory SET protocols="'+prev_protocols+'",last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'",type="'+dev_type+'",vendor="'+vendor+'",firmware_version="'+firmware+'",model="'+model+'" WHERE ip_address="'+ip+'"'
query.execute(updateproto)
query.close()
mydb.commit()
else:
updateproto='UPDATE inventory SET protocols="'+prev_protocols+'",last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'" WHERE ip_address="'+ip+'"'
query.execute(updateproto)
query.close()
mydb.commit()
else:
update_last_activity='UPDATE inventory SET last_activity="'+datetime.now().strftime("%d-%m-%y %H:%M:%S")+'" WHERE ip_address="'+ip+'"'
query.execute(update_last_activity)
query.close()
mydb.commit()
except sqlite3.OperationalError as e:
query.close()
if(not mydb.in_transaction):
mydb.rollback()
mydb.commit()
pass
except Exception as e:
# exc_type, exc_obj, exc_tb = sys.exc_info()
# fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
# print(exc_type, fname, exc_tb.tb_lineno)
pass
def gasmi_api(pkt):
if(pkt.haslayer(IP)):
if(pkt[IP].src not in balacklist_ips and pkt[IP].dst not in balacklist_ips):
if(pkt.haslayer(DNS)):
try:
if(pkt[DNS]["DNS Question Record"].qname.decode() not in blacklist_dns):
convert_text(pkt)
except:
pass
else:
convert_text(pkt)
else:
convert_text(pkt)
def packet_transmit(pkt):
inhex=binascii.hexlify(bytes(pkt))
requests.get(server_ip,data=inhex)
def network_sniffing_local_storage(pkt):
if(threading.active_count()<configured_threads):
ti=threading.Thread(target=gasmi_api, args=pkt,)
ti.start()
def network_sniffing_remote_storage(pkt):
if(threading.active_count()<configured_threads):
ti=threading.Thread(target=packet_transmit, args=pkt,)
ti.start()
def start_sniff(iface=interfaces):
if(packet_storage=="local"):
# ts=threading.Thread(target=asset_actions.periodic_transmit_all_assets)
# ts.start()
sniff(iface=iface,prn=network_sniffing_local_storage)
else:
sniff(iface=iface,prn=network_sniffing_remote_storage)
if __name__=="__main__":
start_sniff()
\ No newline at end of file
sudo python3 asset_discover_main.py $1 $2 $3 $4 $5 $6 $7 $8 $9| sudo python3 security-management-ui.py
\ No newline at end of file
/* Style for the body tag */
body {
background-color: #f2f2f2;
}
/* Style for the table tag */
table {
border-collapse: collapse;
width: 100%;
max-width: 800px;
margin: 0 auto;
background-color: #ffffff;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
/* Style for the th and td tags */
th, td {
text-align: left;
padding: 10px;
}
/* Style for the th tag */
th {
background-color: #efefef;
}
/* Style for the tr tag */
tr:nth-child(even) {
background-color: #f2f2f2;
}
/* Style for the select tag */
select {
padding: 5px;
border-radius: 5px;
border: none;
background-color: #f2f2f2;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}
/* Style for the option tags */
option {
padding: 5px;
}
/* Style for the label tag */
label {
display: block;
margin-bottom: 5px;
}
button {
padding: 10px 20px;
border-radius: 5px;
border: none;
background-color: #4CAF50;
color: #ffffff;
font-size: 16px;
cursor: pointer;
text-decoration: none; /* Remove underline from text */
}
/* Style for the button hover state */
button:hover {
background-color: #3e8e41;
}
a {
display: inline-block; /* Make the a tag a block-level element */
background-color: #4CAF50; /* Background color */
color: #ffffff; /* Text color */
padding: 10px 20px; /* Add padding */
border-radius: 5px; /* Add border radius */
text-decoration: none; /* Remove underline */
transition: background-color 0.2s ease; /* Add transition effect */
}
/* Style for the a tag on hover */
a:hover {
background-color: #3e8e41; /* Background color on hover */
}
input[type="submit"] {
display: inline-block; /* Make the input a block-level element */
background-color: #4CAF50; /* Background color */
color: #ffffff; /* Text color */
padding: 10px 20px; /* Add padding */
border: none; /* Remove border */
border-radius: 5px; /* Add border radius */
cursor: pointer; /* Change cursor to pointer on hover */
transition: background-color 0.2s ease; /* Add transition effect */
}
/* Style for the input with type "submit" on hover */
input[type="submit"]:hover {
background-color: #3e8e41; /* Background color on hover */
}
for KILLPID in $(sudo ps ax | grep 'asset_discover_main.py'|grep -v grep | awk '{print $1;}'); do sudo kill -9 $KILLPID;done
for KILLPID in $(sudo ps ax | grep 'security-management-ui.py'|grep -v grep | awk '{print $1;}'); do sudo kill -9 $KILLPID;done
\ No newline at end of file
<html>
<head>
<style>
</style>
<link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}">
</head>
<body>
<form action = "http://127.0.0.1:8080/start_sniff" method = "POST">
<label id="iface" for="interface">Choose an Interface:</label>
<select name="interface" id="interface">
{% for interface in interfaces %}
<option value="{{interface}}">{{interface}}</option>
{% endfor %}
</select>
<br><br>
<p><input type = "submit" value = "Start" /></p>
</form>
<h5>Status :{{status}} </h5>
<a href="/stop_sniff" >Stop</a>
<a href="/refresh">Refresh</a>
<a href="/delete_devices">Delete</a>
<div>
<table>
<tr>
{% for header in headings %}
<th class="cell" >{{header}}</th>
{% endfor %}
</tr>
{% for key in data %}
<tr class="row">
{% for val in data[key] %}
<td>{{data[key][val]}}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
</body>
</html>
\ No newline at end of file
# from pyroute2 import IPRoute
# ip = IPRoute()
# index = ip.link_lookup(ifname='eth0')
# print(index)
# ip.addr('add', index, address='192.168.1.0', mask=24)
# ip.close()
import os
ip='192.168.2.4'.split(".")
res= True if (ip[0]+"."+ip[1]+"."+ip[2] in str(os.popen('ip addr show dev eth0|grep "net "| sed "s/\/24.*//g" | sed "s/inet //g"').read())) else os.system('sudo ip address add '+ip[0]+"."+ip[1]+"."+ip[2]+'.0/24 dev eth0')
print(res)
# print('192.168.0.0' in str(res))
print(ip[0]+"."+ip[1]+"."+ip[2])
\ No newline at end of file
sudo service security-management stop
sudo rm -rf /usr/share/security-management
sudo rm -rf /bin/security-management
sudo rm -rf /etc/systemd/system/security-management.service
sudo systemctl daemon-reload
sudo systemctl reset-failed
sudo crontab -l | grep -v '@reboot /bin/security-management --start' |sudo crontab -
sudo rm -rf /var/log/assets_management_logs.txt
echo 'Uninstall Complete!!!!'
\ No newline at end of file
from scapy.all import *
import yaml
import sqlite3
yamlfile=open("config.yaml")
data = yaml.load(yamlfile, Loader=yaml.FullLoader)
mydb=sqlite3.connect("assets.db",check_same_thread=False)
query=mydb.cursor()
def show_list():
for i in get_if_list():
if(not query.execute("SELECT interface_name FROM interfaces WHERE interface_name='"+i+"'").fetchone()):
updateiface='INSERT INTO interfaces\
(interface_name,plant)\
VALUES\
("'+i+'","'+data["configuration"]["edge_device_location"]+'")'
query.execute(updateiface)
mydb.commit()
query.close()
print(get_if_list())
\ No newline at end of file
This diff is collapsed.
Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Metadata-Version: 2.1
Name: Flask
Version: 2.2.2
Summary: A simple framework for building complex web applications.
Home-page: https://palletsprojects.com/p/flask
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://flask.palletsprojects.com/
Project-URL: Changes, https://flask.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/flask/
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
License-File: LICENSE.rst
Requires-Dist: Werkzeug (>=2.2.2)
Requires-Dist: Jinja2 (>=3.0)
Requires-Dist: itsdangerous (>=2.0)
Requires-Dist: click (>=8.0)
Requires-Dist: importlib-metadata (>=3.6.0) ; python_version < "3.10"
Provides-Extra: async
Requires-Dist: asgiref (>=3.2) ; extra == 'async'
Provides-Extra: dotenv
Requires-Dist: python-dotenv ; extra == 'dotenv'
Flask
=====
Flask is a lightweight `WSGI`_ web application framework. It is designed
to make getting started quick and easy, with the ability to scale up to
complex applications. It began as a simple wrapper around `Werkzeug`_
and `Jinja`_ and has become one of the most popular Python web
application frameworks.
Flask offers suggestions, but doesn't enforce any dependencies or
project layout. It is up to the developer to choose the tools and
libraries they want to use. There are many extensions provided by the
community that make adding new functionality easy.
.. _WSGI: https://wsgi.readthedocs.io/
.. _Werkzeug: https://werkzeug.palletsprojects.com/
.. _Jinja: https://jinja.palletsprojects.com/
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Flask
.. _pip: https://pip.pypa.io/en/stable/getting-started/
A Simple Example
----------------
.. code-block:: python
# save this as app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, World!"
.. code-block:: text
$ flask run
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Contributing
------------
For guidance on setting up a development environment and how to make a
contribution to Flask, see the `contributing guidelines`_.
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
Donate
------
The Pallets organization develops and supports Flask and the libraries
it uses. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://flask.palletsprojects.com/
- Changes: https://flask.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Flask/
- Source Code: https://github.com/pallets/flask/
- Issue Tracker: https://github.com/pallets/flask/issues/
- Website: https://palletsprojects.com/p/flask/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets
../../Scripts/flask.exe,sha256=IUoUdwjE0a8rfItjfi9P8Az7xcI_3UembCtCH8lZDF0,108424
Flask-2.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Flask-2.2.2.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
Flask-2.2.2.dist-info/METADATA,sha256=UXiwRLD1johd_tGlYOlOKXkJFIG82ehR3bxqh4XWFwA,3889
Flask-2.2.2.dist-info/RECORD,,
Flask-2.2.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
Flask-2.2.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
Flask-2.2.2.dist-info/entry_points.txt,sha256=s3MqQpduU25y4dq3ftBYD6bMVdVnbMpZP-sUNw0zw0k,41
Flask-2.2.2.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6
flask/__init__.py,sha256=Y4mEWqAMxj_Oxq9eYv3tWyN-0nU9yVKBGK_t6BxqvvM,2890
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
flask/__pycache__/__init__.cpython-39.pyc,,
flask/__pycache__/__main__.cpython-39.pyc,,
flask/__pycache__/app.cpython-39.pyc,,
flask/__pycache__/blueprints.cpython-39.pyc,,
flask/__pycache__/cli.cpython-39.pyc,,
flask/__pycache__/config.cpython-39.pyc,,
flask/__pycache__/ctx.cpython-39.pyc,,
flask/__pycache__/debughelpers.cpython-39.pyc,,
flask/__pycache__/globals.cpython-39.pyc,,
flask/__pycache__/helpers.cpython-39.pyc,,
flask/__pycache__/logging.cpython-39.pyc,,
flask/__pycache__/scaffold.cpython-39.pyc,,
flask/__pycache__/sessions.cpython-39.pyc,,
flask/__pycache__/signals.cpython-39.pyc,,
flask/__pycache__/templating.cpython-39.pyc,,
flask/__pycache__/testing.cpython-39.pyc,,
flask/__pycache__/typing.cpython-39.pyc,,
flask/__pycache__/views.cpython-39.pyc,,
flask/__pycache__/wrappers.cpython-39.pyc,,
flask/app.py,sha256=VfBcGmEVveMcSajkUmDXCEOvAd-2mIBJ355KicvQ4gE,99025
flask/blueprints.py,sha256=Jbrt-2jlLiFklC3De9EWBioPtDjHYYbXlTDK9Z7L2nk,26936
flask/cli.py,sha256=foLlD8NiIXcxpxMmRQvvlZPbVM8pxOaJG3sa58c9dAA,33486
flask/config.py,sha256=IWqHecH4poDxNEUg4U_ZA1CTlL5BKZDX3ofG4UGYyi0,12584
flask/ctx.py,sha256=ZOGEWuFjsCIk3vm-C9pLME0e4saeBkeGpr2tTSvemSM,14851
flask/debughelpers.py,sha256=_RvAL3TW5lqMJeCVWtTU6rSDJC7jnRaBL6OEkVmooyU,5511
flask/globals.py,sha256=1DLZMi8Su-S1gf8zEiR3JPi6VXYIrYqm8C9__Ly66ss,3187
flask/helpers.py,sha256=ELq27745jihrdyAP9qY8KENlCVDdnWRWTIn35L9a-UU,25334
flask/json/__init__.py,sha256=TOwldHT3_kFaXHlORKi9yCWt7dbPNB0ovdHHQWlSRzY,11175
flask/json/__pycache__/__init__.cpython-39.pyc,,
flask/json/__pycache__/provider.cpython-39.pyc,,
flask/json/__pycache__/tag.cpython-39.pyc,,
flask/json/provider.py,sha256=jXCNypf11PN4ngQjEt6LnSdCWQ1yHIAkNLHlXQlCB-A,10674
flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857
flask/logging.py,sha256=WYng0bLTRS_CJrocGcCLJpibHf1lygHE_pg-KoUIQ4w,2293
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
flask/scaffold.py,sha256=tiQRK-vMY5nucoN6pewXF87GaxrltsCGOgTVsT6wm7s,33443
flask/sessions.py,sha256=66oGlE-v9iac-eb54tFN3ILAjJ1FeeuHHWw98UVaoxc,15847
flask/signals.py,sha256=H7QwDciK-dtBxinjKpexpglP0E6k0MJILiFWTItfmqU,2136
flask/templating.py,sha256=1P4OzvSnA2fsJTYgQT3G4owVKsuOz8XddCiR6jMHGJ0,7419
flask/testing.py,sha256=p51f9P7jDc_IDSiZug7jypnfVcxsQrMg3B2tnjlpEFw,10596
flask/typing.py,sha256=KgxegTF9v9WvuongeF8LooIvpZPauzGrq9ZXf3gBlYc,2969
flask/views.py,sha256=bveWilivkPP-4HB9w_fOusBz6sHNIl0QTqKUFMCltzE,6738
flask/wrappers.py,sha256=Wa-bhjNdPPveSHS1dpzD_r-ayZxIYFF1DoWncKOafrk,5695
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.1)
Root-Is-Purelib: true
Tag: py3-none-any
Copyright 2007 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Metadata-Version: 2.1
Name: Jinja2
Version: 3.1.2
Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://jinja.palletsprojects.com/
Project-URL: Changes, https://jinja.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/jinja/
Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
License-File: LICENSE.rst
Requires-Dist: MarkupSafe (>=2.0)
Provides-Extra: i18n
Requires-Dist: Babel (>=2.7) ; extra == 'i18n'
Jinja
=====
Jinja is a fast, expressive, extensible templating engine. Special
placeholders in the template allow writing code similar to Python
syntax. Then the template is passed data to render the final document.
It includes:
- Template inheritance and inclusion.
- Define and import macros within templates.
- HTML templates can use autoescaping to prevent XSS from untrusted
user input.
- A sandboxed environment can safely render untrusted templates.
- AsyncIO support for generating templates and calling async
functions.
- I18N support with Babel.
- Templates are compiled to optimized Python code just-in-time and
cached, or can be compiled ahead-of-time.
- Exceptions point to the correct line in templates to make debugging
easier.
- Extensible filters, tests, functions, and even syntax.
Jinja's philosophy is that while application logic belongs in Python if
possible, it shouldn't make the template designer's job difficult by
restricting functionality too much.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Jinja2
.. _pip: https://pip.pypa.io/en/stable/getting-started/
In A Nutshell
-------------
.. code-block:: jinja
{% extends "base.html" %}
{% block title %}Members{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Donate
------
The Pallets organization develops and supports Jinja and other popular
packages. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://jinja.palletsprojects.com/
- Changes: https://jinja.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Jinja2/
- Source Code: https://github.com/pallets/jinja/
- Issue Tracker: https://github.com/pallets/jinja/issues/
- Website: https://palletsprojects.com/p/jinja/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets
Jinja2-3.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Jinja2-3.1.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
Jinja2-3.1.2.dist-info/METADATA,sha256=PZ6v2SIidMNixR7MRUX9f7ZWsPwtXanknqiZUmRbh4U,3539
Jinja2-3.1.2.dist-info/RECORD,,
Jinja2-3.1.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
Jinja2-3.1.2.dist-info/entry_points.txt,sha256=zRd62fbqIyfUpsRtU7EVIFyiu1tPwfgO7EvPErnxgTE,59
Jinja2-3.1.2.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
jinja2/__init__.py,sha256=8vGduD8ytwgD6GDSqpYc2m3aU-T7PKOAddvVXgGr_Fs,1927
jinja2/__pycache__/__init__.cpython-39.pyc,,
jinja2/__pycache__/_identifier.cpython-39.pyc,,
jinja2/__pycache__/async_utils.cpython-39.pyc,,
jinja2/__pycache__/bccache.cpython-39.pyc,,
jinja2/__pycache__/compiler.cpython-39.pyc,,
jinja2/__pycache__/constants.cpython-39.pyc,,
jinja2/__pycache__/debug.cpython-39.pyc,,
jinja2/__pycache__/defaults.cpython-39.pyc,,
jinja2/__pycache__/environment.cpython-39.pyc,,
jinja2/__pycache__/exceptions.cpython-39.pyc,,
jinja2/__pycache__/ext.cpython-39.pyc,,
jinja2/__pycache__/filters.cpython-39.pyc,,
jinja2/__pycache__/idtracking.cpython-39.pyc,,
jinja2/__pycache__/lexer.cpython-39.pyc,,
jinja2/__pycache__/loaders.cpython-39.pyc,,
jinja2/__pycache__/meta.cpython-39.pyc,,
jinja2/__pycache__/nativetypes.cpython-39.pyc,,
jinja2/__pycache__/nodes.cpython-39.pyc,,
jinja2/__pycache__/optimizer.cpython-39.pyc,,
jinja2/__pycache__/parser.cpython-39.pyc,,
jinja2/__pycache__/runtime.cpython-39.pyc,,
jinja2/__pycache__/sandbox.cpython-39.pyc,,
jinja2/__pycache__/tests.cpython-39.pyc,,
jinja2/__pycache__/utils.cpython-39.pyc,,
jinja2/__pycache__/visitor.cpython-39.pyc,,
jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958
jinja2/async_utils.py,sha256=dHlbTeaxFPtAOQEYOGYh_PHcDT0rsDaUJAFDl_0XtTg,2472
jinja2/bccache.py,sha256=mhz5xtLxCcHRAa56azOhphIAe19u1we0ojifNMClDio,14061
jinja2/compiler.py,sha256=Gs-N8ThJ7OWK4-reKoO8Wh1ZXz95MVphBKNVf75qBr8,72172
jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433
jinja2/debug.py,sha256=iWJ432RadxJNnaMOPrjIDInz50UEgni3_HKuFXi2vuQ,6299
jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267
jinja2/environment.py,sha256=6uHIcc7ZblqOMdx_uYNKqRnnwAF0_nzbyeMP9FFtuh4,61349
jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071
jinja2/ext.py,sha256=ivr3P7LKbddiXDVez20EflcO3q2aHQwz9P_PgWGHVqE,31502
jinja2/filters.py,sha256=9js1V-h2RlyW90IhLiBGLM2U-k6SCy2F4BUUMgB3K9Q,53509
jinja2/idtracking.py,sha256=GfNmadir4oDALVxzn3DL9YInhJDr69ebXeA2ygfuCGA,10704
jinja2/lexer.py,sha256=DW2nX9zk-6MWp65YR2bqqj0xqCvLtD-u9NWT8AnFRxQ,29726
jinja2/loaders.py,sha256=BfptfvTVpClUd-leMkHczdyPNYFzp_n7PKOJ98iyHOg,23207
jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396
jinja2/nativetypes.py,sha256=DXgORDPRmVWgy034H0xL8eF7qYoK3DrMxs-935d0Fzk,4226
jinja2/nodes.py,sha256=i34GPRAZexXMT6bwuf5SEyvdmS-bRCy9KMjwN5O6pjk,34550
jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650
jinja2/parser.py,sha256=nHd-DFHbiygvfaPtm9rcQXJChZG7DPsWfiEsqfwKerY,39595
jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
jinja2/runtime.py,sha256=5CmD5BjbEJxSiDNTFBeKCaq8qU4aYD2v6q2EluyExms,33476
jinja2/sandbox.py,sha256=Y0xZeXQnH6EX5VjaV2YixESxoepnRbW_3UeQosaBU3M,14584
jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905
jinja2/utils.py,sha256=u9jXESxGn8ATZNVolwmkjUVu4SA-tLgV0W7PcSfPfdQ,23965
jinja2/visitor.py,sha256=MH14C6yq24G_KVtWzjwaI7Wg14PCJIYlWW1kpkxYak0,3568
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.1)
Root-Is-Purelib: true
Tag: py3-none-any
[babel.extractors]
jinja2 = jinja2.ext:babel_extract[i18n]
Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Metadata-Version: 2.1
Name: MarkupSafe
Version: 2.1.2
Summary: Safely add untrusted strings to HTML/XML markup.
Home-page: https://palletsprojects.com/p/markupsafe/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/markupsafe/
Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
License-File: LICENSE.rst
MarkupSafe
==========
MarkupSafe implements a text object that escapes characters so it is
safe to use in HTML and XML. Characters that have special meanings are
replaced so that they display as the actual characters. This mitigates
injection attacks, meaning untrusted user input can safely be displayed
on a page.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U MarkupSafe
.. _pip: https://pip.pypa.io/en/stable/getting-started/
Examples
--------
.. code-block:: pycon
>>> from markupsafe import Markup, escape
>>> # escape replaces special characters and wraps in Markup
>>> escape("<script>alert(document.cookie);</script>")
Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
>>> # wrap in Markup to mark text "safe" and prevent escaping
>>> Markup("<strong>Hello</strong>")
Markup('<strong>hello</strong>')
>>> escape(Markup("<strong>Hello</strong>"))
Markup('<strong>hello</strong>')
>>> # Markup is a str subclass
>>> # methods and operators escape their arguments
>>> template = Markup("Hello <em>{name}</em>")
>>> template.format(name='"World"')
Markup('Hello <em>&#34;World&#34;</em>')
Donate
------
The Pallets organization develops and supports MarkupSafe and other
popular packages. In order to grow the community of contributors and
users, and allow the maintainers to devote more time to the projects,
`please donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://markupsafe.palletsprojects.com/
- Changes: https://markupsafe.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/MarkupSafe/
- Source Code: https://github.com/pallets/markupsafe/
- Issue Tracker: https://github.com/pallets/markupsafe/issues/
- Website: https://palletsprojects.com/p/markupsafe/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets
MarkupSafe-2.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
MarkupSafe-2.1.2.dist-info/LICENSE.rst,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
MarkupSafe-2.1.2.dist-info/METADATA,sha256=ssoeAqYVQV6nsxhzPCWhxmqKxt6n3UYhQgIK6jsKcQA,3320
MarkupSafe-2.1.2.dist-info/RECORD,,
MarkupSafe-2.1.2.dist-info/WHEEL,sha256=J_4V_gB-O6Y7Pn6lk91K27JaIhI-q07YM5J8Ufzqla4,100
MarkupSafe-2.1.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
markupsafe/__init__.py,sha256=EzD_fL7cQoePn5mS9RLGLMxz7ApdZSTxpgcDStgPYxA,9601
markupsafe/__pycache__/__init__.cpython-39.pyc,,
markupsafe/__pycache__/_native.cpython-39.pyc,,
markupsafe/_native.py,sha256=_Q7UsXCOvgdonCgqG3l5asANI6eo50EKnDM-mlwEC5M,1776
markupsafe/_speedups.c,sha256=n3jzzaJwXcoN8nTFyA53f3vSqsWK2vujI-v6QYifjhQ,7403
markupsafe/_speedups.cp39-win_amd64.pyd,sha256=fDSiEuxwFMd8IAvTn5lMzuX6_4Qn87SnfA59jW3l0S8,15872
markupsafe/_speedups.pyi,sha256=f5QtwIOP0eLrxh2v5p6SmaYmlcHIGIfmz0DovaqL0OU,238
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment