diff --git a/.vscode/launch.json b/.vscode/launch.json index fb4ae3d..497cae7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,30 @@ "console": "integratedTerminal", "justMyCode": true }, + { + "name": "checkdmarc --skip-tls cardinalhealth.com", + "type": "python", + "request": "launch", + "module": "checkdmarc._cli", + "args": [ + "--skip-tls", + "cardinalhealth.com" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "checkdmarc --skip-tls ally.com", + "type": "python", + "request": "launch", + "module": "checkdmarc._cli", + "args": [ + "--skip-tls", + "ally.com" + ], + "console": "integratedTerminal", + "justMyCode": true + }, { "name": "checkdmarc --skip-tls dhs.gov", "type": "python", diff --git a/CHANGELOG.md b/CHANGELOG.md index 283c97c..a7546cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ Changelog ========= +5.6.0 +----- + +- Automatically check for a BIMI DNS record at the `default` selector when using the CLI +- Fix parsing of BIMI record tags when they are separated by a `;` without a space +- Validate the file at the URL in the BIMI `l` tag value + - Must be a SVG file + - The SVG version must be `1.2` + - The SVG base profile must be `tiny-ps` + - The SVG dimensions must be square + - The file size must not exceed 32 KB + +**Note**: This does not currently include certificate validation. + 5.5.1 ----- diff --git a/checkdmarc/_cli.py b/checkdmarc/_cli.py index e6ca89a..492cf88 100644 --- a/checkdmarc/_cli.py +++ b/checkdmarc/_cli.py @@ -57,9 +57,8 @@ def _main(): type=float, default=2.0) arg_parser.add_argument("-b", "--bimi-selector", - default=None, - help="Check for a BIMI record at the provided " - "selector") + default="default", + help="the BIMI selector to use") arg_parser.add_argument("-v", "--version", action="version", version=__version__) arg_parser.add_argument("-w", "--wait", type=float, diff --git a/checkdmarc/_constants.py b/checkdmarc/_constants.py index a15e5fb..05c765b 100644 --- a/checkdmarc/_constants.py +++ b/checkdmarc/_constants.py @@ -18,7 +18,7 @@ See the License for the specific language governing permissions and limitations under the License.""" -__version__ = "5.5.1" +__version__ = "5.6.0" OS = platform.system() OS_RELEASE = platform.release() diff --git a/checkdmarc/bimi.py b/checkdmarc/bimi.py index 0bf186e..9128380 100644 --- a/checkdmarc/bimi.py +++ b/checkdmarc/bimi.py @@ -6,9 +6,11 @@ import logging import re from collections import OrderedDict +from sys import getsizeof import dns import requests +import xmltodict from pyleri import (Grammar, Regex, Sequence, @@ -355,19 +357,46 @@ def parse_bimi_record( if include_tag_descriptions: tags[tag]["name"] = bimi_tags[tag]["name"] tags[tag]["description"] = bimi_tags[tag]["description"] - if tag == "a" and tag_value != "": + if tag == "l" and tag_value != "": try: response = session.get(tag_value) response.raise_for_status() + raw_xml = response.text except Exception as e: - warnings.append(f"Unable to download Authority Evidence at " + warnings.append(f"Unable to download " f"{tag_value} - {str(e)}") - elif tag == "e" and tag_value != "": + try: + if isinstance(raw_xml, bytes): + raw_xml = raw_xml.decode(errors="ignore") + xml = xmltodict.parse(raw_xml) + if "svg" not in xml.keys(): + warnings.append(f"The file at {tag_value} is not a SVG file") + else: + svg = xml["svg"] + version = svg["@version"] + base_profile = None + if "base_profile" in svg.keys(): + base_profile = svg["@baseProfile"] + view_box = svg["@viewBox"] + view_box = view_box.split(" ") + width = int(view_box[-2]) + height = int(view_box[-1]) + if version != "1.2": + warnings.append(f"The SVG version must be 1.2, not {version}") + if base_profile != "tiny-ps": + warnings.append(f"The SVG base profile must be tiny-ps") + if width != height: + warnings.append("The SVG dimensions must be square, not {width}x{height}") + if getsizeof(raw_xml) > 32000: + warnings.append("The SVG file exceeds to maximum size of 32 KB") + except Exception as e: + warnings.append(f"Not a SVG file: {str(e)}") + elif tag == "a" and tag_value != "": try: response = session.get(tag_value) response.raise_for_status() except Exception as e: - warnings.append(f"Unable to download " + warnings.append(f"Unable to download Authority Evidence at " f"{tag_value} - {str(e)}") return OrderedDict(tags=tags, warnings=warnings) diff --git a/checkdmarc/utils.py b/checkdmarc/utils.py index 056b12a..0302ab2 100644 --- a/checkdmarc/utils.py +++ b/checkdmarc/utils.py @@ -30,8 +30,7 @@ WSP_REGEX = r"[ \t]" HTTPS_REGEX = ( - r"https://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F]" - r"[0-9a-fA-F]))+" + r"(https:\/\/)([\w\-]+\.)+[\w-]+([\w\- ,.\/?%&=]*)" ) MAILTO_REGEX_STRING = ( r"^(mailto):([\w\-!#$%&'*+-/=?^_`{|}~]" diff --git a/docs/source/cli.md b/docs/source/cli.md index 4dde90d..7c0b287 100644 --- a/docs/source/cli.md +++ b/docs/source/cli.md @@ -28,7 +28,7 @@ options: -t TIMEOUT, --timeout TIMEOUT number of seconds to wait for an answer from DNS (default 2.0) -b BIMI_SELECTOR, --bimi-selector BIMI_SELECTOR - Check for a BIMI record at the provided selector + The BIMI selector to use (default default) -v, --version show program's version number and exit -w WAIT, --wait WAIT number of seconds to wait between checking domains (default 0.0) --skip-tls skip TLS/SSL testing diff --git a/pyproject.toml b/pyproject.toml index 893a612..b167ca1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dependencies = [ "pyleri>=1.3.2", "requests>=2.25.0", "timeout-decorator>=0.4.1", + "xml2dict>=0.2.2", ] [project.scripts] diff --git a/requirements.txt b/requirements.txt index 8a2de43..707cbd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ expiringdict>=1.1.4 pyleri>=1.3.2 publicsuffixlist>=0.10.0 requests>=2.25.0 +xml2dict>=0.2.2 timeout-decorator>=0.5.0 flake8>=3.8.4 sphinx>=3.3.1 @@ -15,3 +16,4 @@ codecov>=2.1.10 autopep8 hatch pytest +ruff