inlinehashes/inlinehashes/app.py

121 lines
3.5 KiB
Python

"""CLI App.
This file contains all the logic for the command line interface of
this library. It makes use of the same tools available for those that
install the package in order to be used programatically.
"""
import argparse
import json
from typing import List
from urllib.error import URLError
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
def build_json_output(inlines: List[inlinehashes.lib.Inline], alg: str) -> Syntax:
"""Build a JSON output from a list of Inline objects."""
out = [
{
"content": i.short_content,
"hash": getattr(i, alg),
"directive": i.directive,
"line": i.line,
"position": i.position,
}
for i in inlines
]
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:
"""Entry point of the command line interface."""
parser = argparse.ArgumentParser()
parser.add_argument("source", help="URL or local HTML file to check")
parser.add_argument(
"-a",
"--alg",
help="Hash algorithm to use (default: sha256)",
default="sha256",
choices=["sha256", "sha384", "sha512"],
)
parser.add_argument(
"-o",
"--output",
help="Format used to write the output (default: table)",
default="table",
choices=["table", "json", "plain"],
)
parser.add_argument(
"-t",
"--target",
help="Target inline content to look for (default: all)",
default="all",
choices=["all", "script-src", "style-src"],
)
args = parser.parse_args()
path = args.source
target = args.target
output_format = args.output
console = Console()
try:
if path.startswith("http://") or path.startswith("https://"):
req = Request(
path,
headers={"User-Agent": f"Inlinehashes[{inlinehashes.__version__}]"},
)
with urlopen(req) as response:
content = response.read()
else:
with open(path, "r") as f:
content = f.read()
except (URLError, OSError) as error:
console.print(error)
console.print(f"Failed to get source: {path}")
exit(1)
inlines = inlinehashes.parse(content, target)
if output_format == "json":
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__":
run_cli()