Files
dromara--domain-admin/domain_admin/utils/domain_util.py
T
2026-07-13 12:31:34 +08:00

274 lines
6.6 KiB
Python

# -*- coding: utf-8 -*-
"""
domain_util.py
"""
from __future__ import print_function, unicode_literals, absolute_import, division
import io
import re
from typing import List
import tldextract
from tldextract.remote import looks_like_ip
from tldextract.tldextract import ExtractResult
from domain_admin.log import logger
from domain_admin.model import domain_info_model
from domain_admin.service import group_service
from domain_admin.utils import file_util, text_util
from domain_admin.utils.cert_util import cert_consts
class ParsedDomain(object):
"""
解析后的domain数据
"""
# str
domain = ''
# str
root_domain = ''
# str
group_name = ''
# int
port = 0
# str
alias = ''
# 标签 list
tags = None
def parse_domain(domain):
"""
解析域名信息
:param domain: str
:return: str / None
"""
ret = re.match('((http(s)?:)?//)?(?P<domain>[\\w\\._:-]+)/?.*?', domain)
if ret:
return ret.groupdict().get("domain")
else:
return None
def parse_domain_from_csv_file(filename):
"""
读取csv文件 适合完整导入
:param filename:
:return: ParsedDomain
"""
with io.open(filename, 'r', encoding='utf-8') as f:
# 标题
first_line = f.readline()
keys = [filed.strip() for filed in first_line.split(',')]
# 内容字段
for line in f.readlines():
values = [filed.strip() for filed in line.split(',')]
item = dict(zip(keys, values))
domain = parse_domain(item.get('域名', ''))
if ':' in domain:
domain, port = domain.split(":")
else:
# SSL默认端口
port = cert_consts.SSL_DEFAULT_PORT
alias = item.get('备注', '').strip(' -')
group_name = item.get('分组', '').strip(' -')
# SSL端口
port = item.get('端口') or port or cert_consts.SSL_DEFAULT_PORT
# 标签
tags = item.get('标签') or None
if tags:
tags = [tag.strip() for tag in tags.split("、") if tag and tag.strip() and tag.strip() != '-']
if domain:
item = ParsedDomain()
item.domain = domain
item.root_domain = get_root_domain(domain)
item.port = int(port)
item.alias = alias
item.group_name = group_name
item.tags = tags
yield item
def parse_domain_from_txt_file(filename):
"""
读取txt文件 适合快速导入
:param filename:
:return: ParsedDomain
"""
with io.open(filename, 'r', encoding='utf-8') as f:
for line in f.readlines():
domain = parse_domain(line.strip())
if ':' in domain:
domain, port = domain.split(":")
else:
# SSL默认端口
port = cert_consts.SSL_DEFAULT_PORT
if domain:
item = ParsedDomain()
item.domain = domain
item.root_domain = get_root_domain(domain)
item.port = int(port)
item.alias = ''
item.group_name = ''
yield item
def parse_domain_from_file(filename, field_mapping):
"""
解析域名文件的工厂方法
:param filename:
:return: ParsedDomain
"""
file_type = file_util.get_filename_ext(filename)
rows = file_util.read_data_from_file(filename)
if file_type == 'txt':
rows = [
{'domain': parse_domain(row.strip())}
for row in rows
]
else:
rows = file_util.convert_to_import(rows, field_mapping)
for item in rows:
domain = parse_domain(item['domain'])
if ':' in domain:
domain, port = domain.split(":")
else:
# SSL默认端口
port = cert_consts.SSL_DEFAULT_PORT
item['domain'] = domain
item['root_domain'] = get_root_domain(domain)
if not item.get('port'):
item['port'] = port
# 标签
item['tags'] = text_util.split_string(item.get('tags_str'))
return rows
# if file_type == 'csv':
# return parse_domain_from_csv_file(filename)
# else:
# return parse_domain_from_txt_file(filename)
def extract_domain(domain):
"""
解析域名
:param domain: str
:return: ExtractResult
"""
return tldextract.extract(domain)
def get_root_domain(domain):
"""
解析出域名和顶级后缀 rootdomain + suffix
:param domain: str
:return: str
"""
extract_result = extract_domain(domain)
return extract_result.registered_domain
# return '.'.join([extract_result.domain, extract_result.suffix])
def get_subdomain(domain):
"""
解析出子域名前缀
:param domain: str
:return: str
"""
extract_result = extract_domain(domain)
return extract_result.subdomain
def is_ipv4(ip):
"""
检测一个字符串是否是ipv4地址
:param ip:
:return: bool
"""
return looks_like_ip(ip)
# if re.match("(\d+\.){3}\d+", ip):
# return True
# else:
# return False
def encode_hostname(hostname):
"""
编码中文域名,英文域名原样返回
:param hostname: str 中文域名
:return: str
"""
return hostname.encode('idna').decode('ascii')
def verify_cert_common_name(common_name, domain):
"""
验证证书
https://www.cnblogs.com/weifeng1463/p/12719027.html
所有品牌的通配符证书 SSL只能支持域名本身及下一级的所有子域名,而不是无限级支持子域名。
:param common_name:
:param domain:
:return:
"""
# logger.debug("%s <=> %s", common_name, domain)
if '*' in common_name:
# 通配符 SSL 证书
common_name_root_domain = get_domain_parent(common_name)
root_domain = get_domain_parent(domain)
return common_name_root_domain == root_domain \
or common_name_root_domain == domain
else:
# 普通证书
return common_name == domain
def get_domain_parent(domain):
"""
获取父级域名
:param domain:
:return:
eg:
www.chinafruitime.com => chinafruitime.com
blog.www.chinafruitime.com => www.chinafruitime.com
"""
if not domain:
return ''
first_dot_index = domain.find('.')
if first_dot_index < 0:
return ''
return domain[first_dot_index + 1:]
if __name__ == '__main__':
# print(get_root_domain("*.juejin.cn"))
# print(get_subdomain("chinafruitime.com"))
print(get_root_domain("org.uk"))
print(get_root_domain("co.uk"))