diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 3e4dc9e..b815400 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -112,6 +112,12 @@ entry: detect-private-key language: python types: [text] +- id: detect-verbose-curl + name: Detect Verbose CURL + description: Detects the presence of verbose options in cURL commands. + entry: detect-verbose-curl + language: python + types: [text] - id: double-quote-string-fixer name: Fix double quoted strings description: This hook replaces double quoted strings with single quoted strings diff --git a/README.md b/README.md index 6c7fba9..2f6eb65 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,9 @@ The following arguments are available: #### `detect-private-key` Checks for the existence of private keys. +#### `detect-verbose-curl` +Checks for the existence of files with verbose cURL commands. + #### `double-quote-string-fixer` This hook replaces double quoted strings with single quoted strings. diff --git a/pre_commit_hooks/detect_verbose_curl.py b/pre_commit_hooks/detect_verbose_curl.py new file mode 100644 index 0000000..dc3e2f2 --- /dev/null +++ b/pre_commit_hooks/detect_verbose_curl.py @@ -0,0 +1,45 @@ +import argparse +import re +from typing import Optional +from typing import Sequence + +CURL_VERBOSE_PATTERN = re.compile( + br'^(.+)?curl(.+)?((\-v\s)|(\--verbose)|(-w)|(\-\-trace))(.+)?', +) + + +def _get_file_verbose_occurrences(filename: str) -> int: + file_verbose_occurrences = 0 + with open(filename, 'rb') as f: + for i, line in enumerate(f, 1): + if CURL_VERBOSE_PATTERN.search(line): + print( + 'Talkative/Verbose cURL command found: ' + '{filename}:{i}:{line}', + ) + file_verbose_occurrences += 1 + return file_verbose_occurrences + + +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*', help='File names to check') + args = parser.parse_args(argv) + + verbose_command_count = 0 + for filename in args.filenames: + verbose_command_count += _get_file_verbose_occurrences(filename) + + if verbose_command_count > 0: + print( + f'Number of talkative/verbose cURL commands:' + f' {verbose_command_count}', + ) + return verbose_command_count + else: + print('No talkative/verbose cURL commands found!') + return 0 + + +if __name__ == '__main__': + exit(main()) diff --git a/setup.cfg b/setup.cfg index 0f7721f..fe299c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,6 +45,7 @@ console_scripts = debug-statement-hook = pre_commit_hooks.debug_statement_hook:main detect-aws-credentials = pre_commit_hooks.detect_aws_credentials:main detect-private-key = pre_commit_hooks.detect_private_key:main + detect-verbose-curl = pre_commit_hooks.detect_verbose_curl:main double-quote-string-fixer = pre_commit_hooks.string_fixer:main end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:main file-contents-sorter = pre_commit_hooks.file_contents_sorter:main diff --git a/tests/detect_verbose_curl_test.py b/tests/detect_verbose_curl_test.py new file mode 100644 index 0000000..396947b --- /dev/null +++ b/tests/detect_verbose_curl_test.py @@ -0,0 +1,85 @@ +from pre_commit_hooks.detect_verbose_curl import main + + +def test_trivial(tmpdir): + f = tmpdir.join('f.sh').ensure() + assert not main((str(f),)) + + +def test_passing(tmpdir): + f = tmpdir.join('f.sh') + f.write_binary( + b'#!/usr/bin/env bash\n' + # setup + b'url=https://api.somesite.com\n' + # api call 1 + b'curl -X GET ${url} -H "X-Custom-Header: pytest-test"\n' + # api call 2 + b'curl -d "{key1:value1, key2:value2}" -H ' + b'"Content-Type: application/json" -X POST ${url} \n' + # test comments + b'# None of these commands have curl verbose - ' + b'this comment should NOT be recognised as -- is missing\n' + b'# The "trace" amd other options should also not be picked up.\n ' + # list the version of curl - should not be picked up. Only + b'curl --version\n' + b'curl -V\n', + ) + assert main((str(f),)) == 0 + + +def test_failing(tmpdir, capsys): + with tmpdir.as_cwd(): + tmpdir.join('f.sh').write_binary( + b'#!/usr/bin/env bash\n' + # setup + b'url=https://api.somesite.com\n' + # Talkative cURL HTTP calls + b'curl -v -X GET ${url} -H "X-Custom-Header: pytest-test"\n' + b'curl -X GET ${url} -H ' + b'"X-Custom-Header: pytest-test" --verbose\n' + b'curl --write-out output.txt -X GET ${url} ' + b'-H "X-Custom-Header: pytest-test"\n', + ) + tmpdir.join('f.groovy').write_binary( + b'#!/usr/bin/env bash\n' + # setup + b'url=https://api.somesite.com\n' + # Talkative cURL HTTP calls + b'curl -d "{key1:value1, key2:value2}" -w output.txt ' + b'-H "Content-Type: application/json" -X POST ${url}\n' + b'curl --trace-ascii ascii.txt -d "{key1:value1, key2:value2}" ' + b'-H "Content-Type: application/json" -X POST ${url}\n' + b'curl -d "{key1:value1, key2:value2}" -X POST ${url} ' + b'--trace trace.txt -H "Content-Type: application/json"\n', + ) + + assert main(('f.sh', 'f.groovy')) == 6 + + out, _ = capsys.readouterr() + assert out == ( + "Talkative/Verbose cURL command found: f.sh:3:b\'curl -v -X GET" + " ${url} -H \"X-Custom-Header: pytest-test\"\\n\'\n" + "Talkative/Verbose cURL command found: f.sh:4:b\'curl -X GET " + "${url} -H \"X-Custom-Header: pytest-test\" --verbose\\n\'\n" + 'Talkative/Verbose cURL command found: ' + "f.sh:5:b\'curl --write-out output.txt" + " -X GET ${url} -H \"X-Custom-Header: pytest-test\"\\n\'\n" + + "Talkative/Verbose cURL command found: f.groovy:3:b\'curl " + "-d \"{key1:value1, key2:value2}\"" + " -w output.txt -H \"Content-Type: application/json\"" + " -X POST ${url}\\n\'\n" + 'Talkative/Verbose cURL command found:' + " f.groovy:4:b\'curl " + '--trace-ascii ascii.txt ' + "-d \"{key1:value1, key2:value2}\"" + " -H \"Content-Type: application/json\" " + "-X POST ${url}\\n\'\n" + "Talkative/Verbose cURL command found: f.groovy:5:b\'curl " + "-d \"{key1:value1, key2:value2}\"" + '-X POST ${url} --trace trace.txt ' + " -H \"Content-Type: application/json\"\\n\'\n" + + 'Number of talkative/verbose cURL commands: 6\n' + )