Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add command-line interface #84

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,64 @@ a dict with three keys:
|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| "unknown_field"<sup>[2]</sup> | "security.txt contains an unknown field. Field {unknown_field} is either a custom field which may not be widely supported, or there is a typo in a standardised field name. |

### Command line interface

To use sectxt from the command line, simply run the command followed by the address of the website or the path to a local file you want to check:

```shell
sectxt <address>
```

Replace `<address>` with the URL of the website or the path to the local file.

#### Options

- **--show-notifications**: Show notifications.
- **--show-recommendations**: Show recommendations.
- **--no-recommend-unknown-fields**: Do not issue notifications for unknown fields.
- **--json**: Output the results in JSON format.

#### Examples

- Basic validation

Validate a security.txt file at a given website:

```shell
sectxt https://example.com
```

- Validating a local file

Validate a local security.txt file:

```shell
sectxt /path/to/local/security.txt
```

- JSON output

Validate a security.txt file and output the results in JSON format. This output will also include message types such as error, notification, and recommendation.

```shell
sectxt --json https://example.com
```

- Show notifications and recommendations

Validate a security.txt file, show notifications, and provide recommendations:

```shell
sectxt --show-notifications --show-recommendations https://example.com
```

- Suppress recommendations related to unknown fields

Validate a security.txt file, show recommendations but suppress notifications for unknown fields:

```shell
sectxt --show-recommendations --no-recommend-unknown-fields https://example.com
```

### Security.txt scraping information

Expand Down
101 changes: 101 additions & 0 deletions sectxt/command_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Module that provides a command-line interface to SecTXT."""

import argparse
from typing import List
from urllib.parse import urlparse

from . import ErrorDict, SecurityTXT, __version__


class TaggedErrorDict(ErrorDict):
tag: str


def valid_url(url: str) -> bool:
"""Basic URL validator that checks for the presence of a scheme and a network location."""
try:
parsed_url = urlparse(url)
return all([parsed_url.scheme, parsed_url.netloc])
except AttributeError:
return False


def tag_messages(
messages_list: List[ErrorDict], message_type: str
) -> List[TaggedErrorDict]:
"""Add message_type to each message in the list as {"tag": message_type}"""

return [dict(msg, **{"tag": message_type}) for msg in messages_list]


def human_readable_print(messages_list: List[TaggedErrorDict]) -> None:
"""Print messages in a human-readable format"""

for message in messages_list:
if message["line"]:
print(
"[" + message["tag"] + "]",
"L" + str(message["line"]) + ":",
message["code"],
":",
message["message"],
)
else:
print("[" + message["tag"] + "]", message["code"], ":", message["message"])

if len(messages_list) == 0:
print("✓ No issues were detected!")


def main() -> int:
"""Main function which parses the arguments, calls SecurityTXT()
and prints output messages.

Returns:
0 if the security.txt file does not contain any errors; otherwise, 1

"""

parser = argparse.ArgumentParser(description="Parse and validate security.txt")
parser.add_argument("address", help="website URL or path to local file to check")
parser.add_argument(
"--json", action="store_true", help="output the results in JSON format"
)
parser.add_argument(
"--no-recommend-unknown-fields",
action="store_true",
help="do not issue notifications for unknown fields",
)
parser.add_argument(
"--show-notifications", action="store_true", help="show notifications"
)
parser.add_argument(
"--show-recommendations", action="store_true", help="show recommendations"
)
parser.add_argument(
"--version", action="version", version="%(prog)s " + __version__
)
args = parser.parse_args()

address_is_local = not valid_url(args.address)

s = SecurityTXT(
args.address,
recommend_unknown_fields=not args.no_recommend_unknown_fields,
is_local=address_is_local,
)

output_messages = tag_messages(s.errors, "ERROR")

if args.show_recommendations:
output_messages += tag_messages(s.recommendations, "REC")

if args.show_notifications:
output_messages += tag_messages(s.notifications, "NOTE")

if args.json:
print(output_messages)
else:
human_readable_print(output_messages)

return 0 if s.is_valid() else 1
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ install_requires =
langcodes==3.3.0
PGPy-dtc==0.1.0
validators==0.32.0

[options.entry_points]
console_scripts =
sectxt = sectxt.command_line:main