diff --git a/.gitignore b/.gitignore
index c1e2195..5a813ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -200,3 +200,6 @@ specs.json
swagger.yaml
swagger.json
*.json
+
+## unknown data
+.DS_Store
diff --git a/README.md b/README.md
index 06c8543..660e455 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
OWASP OFFAT (OFFensive Api Tester) is created to automatically test API for common vulnerabilities after generating tests from openapi specification file. It provides feature to automatically fuzz inputs and use user provided inputs during tests specified via YAML config file.
-![UnDocumented petstore API endpoint HTTP method results](./assets/images/tests/offat-v0.5.0.png)
+![UnDocumented petstore API endpoint HTTP method results](https://owasp.org/OFFAT/assets/images/tests/offat-v0.5.0.png)
## Demo
diff --git a/assets/images/logos/offat-2.png b/assets/images/logos/offat-2.png
new file mode 100644
index 0000000..515cdad
Binary files /dev/null and b/assets/images/logos/offat-2.png differ
diff --git a/assets/images/logos/offat-3.png b/assets/images/logos/offat-3.png
new file mode 100644
index 0000000..293ac06
Binary files /dev/null and b/assets/images/logos/offat-3.png differ
diff --git a/src/MANIFEST.in b/src/MANIFEST.in
index 641eeb8..b0b9074 100644
--- a/src/MANIFEST.in
+++ b/src/MANIFEST.in
@@ -2,3 +2,5 @@ include README.md
include LICENSE
include SECURITY.md
include DISCLAIMER.md
+
+include offat/report/templates/report.html
\ No newline at end of file
diff --git a/src/README.md b/src/README.md
index 27b7140..b688026 100644
--- a/src/README.md
+++ b/src/README.md
@@ -145,6 +145,16 @@ The disclaimer advises users to use the open-source project for ethical and legi
offat -h
```
+- Save result in `json`, `yaml` or `html` formats.
+
+ ```bash
+ offat -f swagger_file.json -o output.html -of html
+ ```
+
+> `json` format is default output format.
+> `yaml` format needs to be sanitized before usage since it dumps data as python objects.
+> `html` format needs more visualization.
+
- Run tests only for endpoint paths matching regex pattern
```bash
diff --git a/src/offat/__main__.py b/src/offat/__main__.py
index 5af4181..16f73b6 100644
--- a/src/offat/__main__.py
+++ b/src/offat/__main__.py
@@ -37,7 +37,8 @@ def start():
parser.add_argument('-rl', '--rate-limit', dest='rate_limit', help='API requests rate limit. -dr should be passed in order to use this option', type=int, default=None, required=False)
parser.add_argument('-dr', '--delay-rate', dest='delay_rate', help='API requests delay rate in seconds. -rl should be passed in order to use this option', type=float, default=None, required=False)
parser.add_argument('-pr','--path-regex', dest='path_regex_pattern', type=str, help='run tests for paths matching given regex pattern', required=False, default=None)
- parser.add_argument('-o', '--output', dest='output_file', type=str, help='path to store test results in json format', required=False, default=None)
+ parser.add_argument('-o', '--output', dest='output_file', type=str, help='path to store test results in specified format. Default format is html', required=False, default=None)
+ parser.add_argument('-of','--format', dest='output_format', type=str, choices=['json', 'yaml','html'], help='Data format to save (json, yaml, html). Default: json', required=False, default='json')
parser.add_argument('-H', '--headers', dest='headers', type=str, help='HTTP requests headers that should be sent during testing eg: User-Agent: offat', required=False, default=None, action='append', nargs='*')
parser.add_argument('-tdc','--test-data-config', dest='test_data_config',help='YAML file containing user test data for tests', required=False, type=str)
parser.add_argument('-p', '--proxy', dest='proxy', help='Proxy server URL to route HTTP requests through (e.g., "http://proxyserver:port")', required=False, type=str)
@@ -70,6 +71,7 @@ def start():
api_parser=api_parser,
regex_pattern=args.path_regex_pattern,
output_file=args.output_file,
+ output_file_format=args.output_format,
req_headers=headers_dict,
rate_limit=rate_limit,
delay=delay_rate,
diff --git a/src/offat/report/__init__.py b/src/offat/report/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/offat/report/generator.py b/src/offat/report/generator.py
new file mode 100644
index 0000000..842bcdd
--- /dev/null
+++ b/src/offat/report/generator.py
@@ -0,0 +1,67 @@
+from offat.report import templates
+from os.path import dirname, join as path_join
+from os import makedirs
+from yaml import dump as yaml_dump
+from json import dumps as json_dumps
+
+from ..logger import create_logger
+
+
+logger = create_logger(__name__)
+
+
+class ReportGenerator:
+ @staticmethod
+ def generate_html_report(results:list[dict]):
+ html_report_template_file_name = 'report.html'
+ html_report_file_path = path_join(dirname(templates.__file__),html_report_template_file_name)
+
+ with open(html_report_file_path, 'r') as f:
+ report_file_content = f.read()
+
+ # TODO: validate report path to avoid injection attacks.
+ if not isinstance(results, list):
+ raise ValueError('results arg expects a list[dict].')
+
+ report_file_content = report_file_content.replace('{ results }', json_dumps(results))
+
+ return report_file_content
+
+ @staticmethod
+ def handle_report_format(results:list[dict], report_format:str) -> str:
+ result = None
+
+ match report_format:
+ case 'html':
+ logger.warning('HTML output format displays only basic data.')
+ result = ReportGenerator.generate_html_report(results=results)
+ case 'yaml':
+ logger.warning('YAML output format needs to be sanitized before using it further.')
+ result = yaml_dump({
+ 'results':results,
+ })
+ case _: # default json format
+ report_format = 'json'
+ result = json_dumps({
+ 'results':results,
+ })
+
+ logger.info(f'Generated {report_format.upper()} format report.')
+ return result
+
+
+ @staticmethod
+ def save_report(report_path:str, report_file_content:str):
+ if report_path != '/':
+ dir_name = dirname(report_path)
+ makedirs(dir_name, exist_ok=True)
+
+ with open(report_path, 'w') as f:
+ logger.info(f'Writing report to file: {report_path}')
+ f.write(report_file_content)
+
+
+ @staticmethod
+ def generate_report(results:list[dict], report_format:str, report_path:str):
+ formatted_results = ReportGenerator.handle_report_format(results=results, report_format=report_format)
+ ReportGenerator.save_report(report_path=report_path, report_file_content=formatted_results)
diff --git a/src/offat/report/templates/__init__.py b/src/offat/report/templates/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/offat/report/templates/report.html b/src/offat/report/templates/report.html
new file mode 100644
index 0000000..16f2333
--- /dev/null
+++ b/src/offat/report/templates/report.html
@@ -0,0 +1,256 @@
+
+
+
+