Add other types of output for the results

This commit is contained in:
Gonçalo Valério 2023-01-25 20:31:34 +00:00
parent 9bf236b60d
commit 20fc87f5cd
Signed by: dethos
GPG Key ID: DF557F2BDCC2445E
4 changed files with 132 additions and 23 deletions

View File

@ -43,18 +43,19 @@ This is the available functionality:
.. code:: .. code::
usage: inlinehashes [-h] [-a {sha256,sha384,sha512}] [-f] [-t {all,scripts,styles}] source usage: inlinehashes [-h] [-a {sha256,sha384,sha512}] [-o {table,json,plain}] [-t {all,script-src,style-src}] source
positional arguments: positional arguments:
source URL or local HTML file to check source URL or local HTML file to check
optional arguments: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-a {sha256,sha384,sha512}, --alg {sha256,sha384,sha512} -a {sha256,sha384,sha512}, --alg {sha256,sha384,sha512}
Hash algorithm to use (default: sha256) Hash algorithm to use (default: sha256)
-f, --full Include full content in the output -o {table,json,plain}, --output {table,json,plain}
-t {all,scripts,styles}, --target {all,scripts,styles} Format used to write the output (default: table)
Target inline content to look for -t {all,script-src,style-src}, --target {all,script-src,style-src}
Target inline content to look for (default: all)
Here is an example of the output: Here is an example of the output:

View File

@ -10,17 +10,19 @@ from typing import List
from urllib.error import URLError from urllib.error import URLError
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from rich import box
from rich.console import Console
from rich.syntax import Syntax
from rich.table import Table
import inlinehashes import inlinehashes
def build_output( def build_json_output(inlines: List[inlinehashes.lib.Inline], alg: str) -> Syntax:
inlines: List[inlinehashes.lib.Inline], alg: str, full: bool = False
) -> str:
"""Build a JSON output from a list of Inline objects.""" """Build a JSON output from a list of Inline objects."""
snippet = "content" if full else "short_content"
out = [ out = [
{ {
"content": getattr(i, snippet), "content": i.short_content,
"hash": getattr(i, alg), "hash": getattr(i, alg),
"directive": i.directive, "directive": i.directive,
"line": i.line, "line": i.line,
@ -28,7 +30,32 @@ def build_output(
} }
for i in inlines for i in inlines
] ]
return json.dumps(out, indent=2) return Syntax(json.dumps(out, indent=2), "JSON", theme="ansi_dark")
def build_plain_output(inlines: List[inlinehashes.lib.Inline], alg: str) -> str:
"""Build a simple output of an inline per line."""
return "\n".join(
[
f"[magenta]{i.directive}[/magenta] [cyan]{i.line}[/cyan] "
f"[green]{i.position}[/green] [default]{getattr(i, alg)}[/default]"
for i in inlines
]
)
def build_table_output(inlines: List[inlinehashes.lib.Inline], alg: str) -> Table:
"""Build a table to output the inlines in a nicer way."""
table = Table(box=box.HORIZONTALS)
table.add_column("Directive", style="magenta")
table.add_column("Line", justify="right", style="cyan")
table.add_column("Position", justify="right", style="green")
table.add_column("Hash")
for i in inlines:
table.add_row(i.directive, str(i.line), str(i.position), getattr(i, alg))
return table
def run_cli() -> None: def run_cli() -> None:
@ -43,21 +70,24 @@ def run_cli() -> None:
choices=["sha256", "sha384", "sha512"], choices=["sha256", "sha384", "sha512"],
) )
parser.add_argument( parser.add_argument(
"-f", "-o",
"--full", "--output",
help="Include full content in the output", help="Format used to write the output (default: table)",
action="store_true", default="table",
choices=["table", "json", "plain"],
) )
parser.add_argument( parser.add_argument(
"-t", "-t",
"--target", "--target",
help="Target inline content to look for", help="Target inline content to look for (default: all)",
default="all", default="all",
choices=["all", "script-src", "style-src"], choices=["all", "script-src", "style-src"],
) )
args = parser.parse_args() args = parser.parse_args()
path = args.source path = args.source
target = args.target target = args.target
output_format = args.output
console = Console()
try: try:
if path.startswith("http://") or path.startswith("https://"): if path.startswith("http://") or path.startswith("https://"):
@ -71,13 +101,19 @@ def run_cli() -> None:
with open(path, "r") as f: with open(path, "r") as f:
content = f.read() content = f.read()
except (URLError, OSError) as error: except (URLError, OSError) as error:
print(error) console.print(error)
print(f"Failed to get source: {path}") console.print(f"Failed to get source: {path}")
exit(1) exit(1)
inlines = inlinehashes.parse(content, target) inlines = inlinehashes.parse(content, target)
out = build_output(inlines, args.alg, bool(args.full)) if output_format == "json":
print(out) out = build_json_output(inlines, args.alg)
elif output_format == "plain":
out = build_plain_output(inlines, args.alg)
else:
out = build_table_output(inlines, args.alg)
console.print(out)
if __name__ == "__main__": if __name__ == "__main__":

73
poetry.lock generated
View File

@ -111,6 +111,43 @@ files = [
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
] ]
[[package]]
name = "markdown-it-py"
version = "2.1.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"},
{file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"]
code-style = ["pre-commit (==2.6)"]
compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"]
linkify = ["linkify-it-py (>=1.0,<2.0)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "0.991" version = "0.991"
@ -229,6 +266,21 @@ files = [
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"] testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pygments"
version = "2.14.0"
description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
{file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
]
[package.extras]
plugins = ["importlib-metadata"]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.2.1" version = "7.2.1"
@ -251,6 +303,25 @@ pluggy = ">=0.12,<2.0"
[package.extras] [package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "rich"
version = "13.2.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "rich-13.2.0-py3-none-any.whl", hash = "sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003"},
{file = "rich-13.2.0.tar.gz", hash = "sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5"},
]
[package.dependencies]
markdown-it-py = ">=2.1.0,<3.0.0"
pygments = ">=2.6.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.0.229" version = "0.0.229"
@ -304,4 +375,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "81f9d3306e76f9f7c39291bd7972216d852f39aba794b43afe2b9a0d4a7a2829" content-hash = "ded8c6b34befe59dd43fd80d0a9027a54c17a97bc734445fb5978235f17cc0ca"

View File

@ -21,6 +21,7 @@ classifiers = [
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.11" python = "^3.11"
beautifulsoup4 = "^4.10.0" beautifulsoup4 = "^4.10.0"
rich = "^13.2.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^7.2.1" pytest = "^7.2.1"