Compare commits

...

3 Commits

6 changed files with 134 additions and 27 deletions

View File

@ -43,24 +43,25 @@ This is the available functionality:
.. 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
optional arguments:
options:
-h, --help show this help message and exit
-a {sha256,sha384,sha512}, --alg {sha256,sha384,sha512}
Hash algorithm to use (default: sha256)
-f, --full Include full content in the output
-t {all,scripts,styles}, --target {all,scripts,styles}
Target inline content to look for
Hash algorithm to use (default: sha256)
-o {table,json,plain}, --output {table,json,plain}
Format used to write the output (default: table)
-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:
.. code::
$inlinehashes https://ovalerio.net -a sha384
$inlinehashes https://ovalerio.net -a sha384 -o json
[
{
"content": "\n html {\n height: 100%;\n }\n ",

View File

@ -1,4 +1,4 @@
from .lib import parse
__version__ = "0.0.4"
__version__ = "0.0.5"
__all__ = ["parse"]

View File

@ -10,17 +10,19 @@ 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_output(
inlines: List[inlinehashes.lib.Inline], alg: str, full: bool = False
) -> str:
def build_json_output(inlines: List[inlinehashes.lib.Inline], alg: str) -> Syntax:
"""Build a JSON output from a list of Inline objects."""
snippet = "content" if full else "short_content"
out = [
{
"content": getattr(i, snippet),
"content": i.short_content,
"hash": getattr(i, alg),
"directive": i.directive,
"line": i.line,
@ -28,7 +30,32 @@ def build_output(
}
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:
@ -43,21 +70,24 @@ def run_cli() -> None:
choices=["sha256", "sha384", "sha512"],
)
parser.add_argument(
"-f",
"--full",
help="Include full content in the output",
action="store_true",
"-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",
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://"):
@ -71,13 +101,17 @@ def run_cli() -> None:
with open(path, "r") as f:
content = f.read()
except (URLError, OSError) as error:
print(error)
print(f"Failed to get source: {path}")
console.print(error)
console.print(f"Failed to get source: {path}")
exit(1)
inlines = inlinehashes.parse(content, target)
out = build_output(inlines, args.alg, bool(args.full))
print(out)
if output_format == "json":
console.print(build_json_output(inlines, args.alg))
elif output_format == "plain":
console.print(build_plain_output(inlines, args.alg))
else:
console.print(build_table_output(inlines, args.alg))
if __name__ == "__main__":

73
poetry.lock generated
View File

@ -111,6 +111,43 @@ files = [
{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]]
name = "mypy"
version = "0.991"
@ -229,6 +266,21 @@ files = [
dev = ["pre-commit", "tox"]
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]]
name = "pytest"
version = "7.2.1"
@ -251,6 +303,25 @@ pluggy = ">=0.12,<2.0"
[package.extras]
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]]
name = "ruff"
version = "0.0.229"
@ -304,4 +375,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "81f9d3306e76f9f7c39291bd7972216d852f39aba794b43afe2b9a0d4a7a2829"
content-hash = "ded8c6b34befe59dd43fd80d0a9027a54c17a97bc734445fb5978235f17cc0ca"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "inlinehashes"
version = "0.0.4"
version = "0.0.5"
description = "Hash generator for HTML inline styles and scripts"
authors = ["Gonçalo Valério <gon@ovalerio.net>"]
homepage = "https://github.com/dethos/inlinehashes"
@ -21,6 +21,7 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.11"
beautifulsoup4 = "^4.10.0"
rich = "^13.2.0"
[tool.poetry.dev-dependencies]
pytest = "^7.2.1"

View File

@ -217,4 +217,4 @@ class TestParse:
def test_version():
assert __version__ == "0.0.4"
assert __version__ == "0.0.5"