mirror of
https://github.com/kellyjonbrazil/jc.git
synced 2026-04-03 17:44:07 +02:00
Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
773b7f4b1f | ||
|
|
d17ffde9cd | ||
|
|
9c57c09c00 | ||
|
|
4d730a9de5 | ||
|
|
fc57bcfce2 | ||
|
|
fa5571486c | ||
|
|
9996c4fe23 | ||
|
|
038d429024 | ||
|
|
9bf6facb0d | ||
|
|
965717886e | ||
|
|
e9bfc3dd29 | ||
|
|
f46b33eacf | ||
|
|
f475fe44df | ||
|
|
5fdbe2962d | ||
|
|
ab291b9eef | ||
|
|
fd411fd772 | ||
|
|
b1e95a60a2 | ||
|
|
bb1439f0d5 | ||
|
|
ba963d98a0 | ||
|
|
83440ccb55 | ||
|
|
796f61bfa4 | ||
|
|
070cac4ae1 | ||
|
|
3ed84f9f42 | ||
|
|
a205afb6f3 | ||
|
|
a6d983dd8f | ||
|
|
b6c8d6d01d | ||
|
|
b5a5d5b133 | ||
|
|
da528e7814 | ||
|
|
4acebf4f62 | ||
|
|
4d40808d2b | ||
|
|
c543f00bd3 | ||
|
|
947cf41dfa | ||
|
|
8d8c58742e | ||
|
|
995ecc9bfb | ||
|
|
21a15225eb | ||
|
|
48921d4584 | ||
|
|
342db45edc | ||
|
|
5f88ecf844 | ||
|
|
a56aebfe70 | ||
|
|
422bb744a8 | ||
|
|
c3b814a15f | ||
|
|
e4574047a0 | ||
|
|
0d7d7951f8 | ||
|
|
da904e4770 | ||
|
|
19b540041a | ||
|
|
f2ffb93eea | ||
|
|
c0c0e05642 | ||
|
|
966978f17e | ||
|
|
8ab08a5231 | ||
|
|
48e534fa03 | ||
|
|
61851c1bd0 | ||
|
|
3c51b2d83d | ||
|
|
ee3a28528e | ||
|
|
916bcdae38 | ||
|
|
efb1d3e6b2 | ||
|
|
4e6ae66bac | ||
|
|
5ee88e7b67 | ||
|
|
c3b68903cb | ||
|
|
fe1f1013a7 | ||
|
|
fb14f5439f | ||
|
|
5ca0fc364e | ||
|
|
a1fe7037e5 | ||
|
|
c2af7d113e | ||
|
|
ff034e401d | ||
|
|
5abe095beb | ||
|
|
33de5f01e6 | ||
|
|
8ce155d843 | ||
|
|
b921d5ec95 | ||
|
|
e21542aaa2 | ||
|
|
6150aae0ae | ||
|
|
f27b35f371 | ||
|
|
d24f9a885d | ||
|
|
48dd82c8d1 | ||
|
|
e57167ad1f | ||
|
|
ada9137642 | ||
|
|
bd428a9fd7 | ||
|
|
934941332f | ||
|
|
0c209dbd10 | ||
|
|
56e041aa26 | ||
|
|
cf9d48582e | ||
|
|
92e2252bee | ||
|
|
fd7861db11 | ||
|
|
35464bbbfb | ||
|
|
db8ddd7f0e | ||
|
|
72207c54ee | ||
|
|
a683f68003 | ||
|
|
8d2d3db3fa | ||
|
|
eca785450d | ||
|
|
5b40a97ce4 | ||
|
|
66cb4e9bde | ||
|
|
d1f33645ca | ||
|
|
5eff65c326 | ||
|
|
929c38715d | ||
|
|
8c91a7b760 | ||
|
|
14289ecd6c | ||
|
|
b833c44783 | ||
|
|
e23aa818ea | ||
|
|
473d919c13 | ||
|
|
9f925d6ac9 | ||
|
|
f6ca32b3a3 | ||
|
|
4cbe84c3d6 | ||
|
|
ffac5bf9d3 | ||
|
|
9aa424f9f8 | ||
|
|
0a8495c68e | ||
|
|
6aea066cf0 | ||
|
|
ff3c2d809e | ||
|
|
145059fc89 | ||
|
|
20e5c19de4 | ||
|
|
4c98fd4b87 | ||
|
|
125e54213e | ||
|
|
0152e0665f | ||
|
|
0679bcbc56 | ||
|
|
5500648aa0 | ||
|
|
bb50caad4d | ||
|
|
7eef5d94d5 | ||
|
|
74f623f8d6 | ||
|
|
f638aca092 | ||
|
|
da35eaf80a | ||
|
|
ade0e8e8fc | ||
|
|
90076090f0 | ||
|
|
23635def8b | ||
|
|
4eeec087bd | ||
|
|
7331961038 | ||
|
|
e4acb3d5b7 | ||
|
|
c60549a994 | ||
|
|
d46fc8bbfa | ||
|
|
b133d1f90d | ||
|
|
5be615a97e | ||
|
|
ea1d820f96 | ||
|
|
47e262cf72 | ||
|
|
eec673be90 | ||
|
|
9a0fb2a7c8 | ||
|
|
b5145d6c14 | ||
|
|
9747ca414d | ||
|
|
312d465b61 | ||
|
|
7dcf87d24a | ||
|
|
cf3cfd16a9 |
17
CHANGELOG
17
CHANGELOG
@@ -1,5 +1,22 @@
|
||||
jc changelog
|
||||
|
||||
20210517 v1.15.4
|
||||
- Update ping parser to support error responses in OSX and BSD
|
||||
- Update ping parser to be more resillient against parsing errors for unknown error types
|
||||
- Update dig parser to support `+noall +answer` use case
|
||||
- Update dig parser compatibility to all platforms
|
||||
- Fix colors in Windows terminals (cmd.exe and PowerShell)
|
||||
- Fix epoch calculations when UTC is referenced as "Coordinated Universal Time"
|
||||
- Add Windows time format for systeminfo output
|
||||
- Add exceptions module to standardize parser exceptions
|
||||
- JC no longer swallows exit codes when using the "magic" syntax. See the Exit Codes section of the README and man page for details
|
||||
|
||||
20210426 v1.15.3
|
||||
- Add ufw status command parser tested on linux
|
||||
- Add ufw-appinfo command parser tested on linux
|
||||
- Fix deb package name to conform to standard
|
||||
- Add Caveats section to readme and manpage
|
||||
|
||||
20210418 v1.15.2
|
||||
- Add systeminfo parser tested on Windows
|
||||
- Update dig parser to fix an issue with IPv6 addresses in the server field
|
||||
|
||||
@@ -17,7 +17,7 @@ Pull requests are the best way to propose changes to the codebase (we use [Githu
|
||||
2. Fork the repo and create your branch from `dev`, if available, otherwise `master`.
|
||||
3. If you've added code that should be tested, add tests. All new parsers should have several sample outputs and tests.
|
||||
4. Documentation is auto-generated from docstrings, so ensure they are clear and accurate.
|
||||
5. Ensure the test suite passes.
|
||||
5. Ensure the test suite passes. (Note: "**America/Los_Angeles**" timezone should be configured on the test system)
|
||||
6. Make sure your code lints.
|
||||
7. Issue that pull request!
|
||||
|
||||
@@ -61,6 +61,11 @@ Good:
|
||||
]
|
||||
```
|
||||
|
||||
## Tests
|
||||
It is essential to have good command output sample coverage and tests to keep the `jc` parser quality high.
|
||||
|
||||
Many parsers include calculated timestamp fields using the `jc.utils.timestamp` class. Naive timestamps created with this class should be generated on a system configured with the "**America/Los_Angeles**" timezone on linux/macOS/unix and "**Pacific Standard Time**" timezone on Windows for tests to pass on the Github Actions CI tests. This timezone should be configured on your local system before running the tests locally, as well.
|
||||
|
||||
## Any contributions you make will be under the MIT Software License
|
||||
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
|
||||
|
||||
|
||||
113
EXAMPLES.md
113
EXAMPLES.md
@@ -732,7 +732,7 @@ dig -x 1.1.1.1 | jc --dig -p # or: jc -p dig -x 1.1.1.1
|
||||
```
|
||||
### dir
|
||||
```bash
|
||||
dir | jc --dir -p # or: jc -p dir
|
||||
dir | jc --dir -p
|
||||
```
|
||||
```json
|
||||
[
|
||||
@@ -1671,7 +1671,7 @@ iw dev wlan0 scan | jc --iw-scan -p # or: jc -p iw dev wlan0 scan
|
||||
```
|
||||
### jobs
|
||||
```bash
|
||||
jobs -l | jc --jobs -p # or: jc -p jobs
|
||||
jobs -l | jc --jobs -p
|
||||
```
|
||||
```json
|
||||
[
|
||||
@@ -3126,6 +3126,115 @@ traceroute -m 3 8.8.8.8 | jc --traceroute -p # or: jc -p traceroute -m
|
||||
]
|
||||
}
|
||||
```
|
||||
### ufw status
|
||||
```bash
|
||||
ufw status verbose | jc --ufw -p # or jc -p ufw status verbose
|
||||
```
|
||||
```json
|
||||
{
|
||||
"status": "active",
|
||||
"logging": "on",
|
||||
"logging_level": "low",
|
||||
"default": "deny (incoming), allow (outgoing), disabled (routed)",
|
||||
"new_profiles": "skip",
|
||||
"rules": [
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "any",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
22
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": 0,
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": 0,
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 65535
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "tcp",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
80,
|
||||
443
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": 0,
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": 0,
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 65535
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
### ufw app info [application]
|
||||
```bash
|
||||
ufw app info MSN | jc --ufw-appinfo -p # or: jc -p ufw app info MSN
|
||||
```
|
||||
```json
|
||||
[
|
||||
{
|
||||
"profile": "MSN",
|
||||
"title": "MSN Chat",
|
||||
"description": "MSN chat protocol (with file transfer and voice)",
|
||||
"tcp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"udp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"tcp_ranges": [
|
||||
{
|
||||
"start": 6891,
|
||||
"end": 6900
|
||||
}
|
||||
],
|
||||
"normalized_tcp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"normalized_tcp_ranges": [
|
||||
{
|
||||
"start": 6891,
|
||||
"end": 6900
|
||||
}
|
||||
],
|
||||
"normalized_udp_list": [
|
||||
1863,
|
||||
6901
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
### uname -a
|
||||
```bash
|
||||
uname -a | jc --uname -p # or: jc -p uname -a
|
||||
|
||||
57
README.md
57
README.md
@@ -62,26 +62,28 @@ The `jc` parsers can also be used as python modules. In this case the output wil
|
||||
```
|
||||
Two representations of the data are possible. The default representation uses a strict schema per parser and converts known numbers to int/float JSON values. Certain known values of `None` are converted to JSON `null`, known boolean values are converted, and, in some cases, additional semantic context fields are added.
|
||||
|
||||
> Note: Some parsers have calculated epoch timestamp fields added to the output. Unless a timestamp field name has a `_utc` suffix it is considered naive. (i.e. based on the local timezone of the system the `jc` parser was run on).
|
||||
>
|
||||
> If a UTC timezone can be detected in the text of the command output, the timestamp will be timezone aware and have a `_utc` suffix on the key name. (e.g. `epoch_utc`) No other timezones are supported for aware timestamps.
|
||||
|
||||
To access the raw, pre-processed JSON, use the `-r` cli option or the `raw=True` function parameter in `parse()`.
|
||||
|
||||
Schemas for each parser can be found at the documentation link beside each parser below.
|
||||
Schemas for each parser can be found at the documentation link beside each [**Parser**](#parsers) below.
|
||||
|
||||
Release notes can be found [here](https://blog.kellybrazil.com/category/jc-news/).
|
||||
|
||||
## Why Would Anyone Do This!?
|
||||
For more information on the motivations for this project, please see my [blog post](https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/).
|
||||
For more information on the motivations for this project, please see my blog post on [Bringing the Unix Philosophy to the 21st Century](https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/).
|
||||
|
||||
See also:
|
||||
- [libxo on FreeBSD](http://juniper.github.io/libxo/libxo-manual.html)
|
||||
- [powershell](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertto-json?view=powershell-7)
|
||||
- [blog: linux apps should have a json flag](https://thomashunter.name/posts/2012-06-06-linux-cli-apps-should-have-a-json-flag)
|
||||
|
||||
Use Cases:
|
||||
- [Bash scripting](https://blog.kellybrazil.com/2021/04/12/practical-json-at-the-command-line/)
|
||||
- [Ansible command output parsing](https://blog.kellybrazil.com/2020/08/30/parsing-command-output-in-ansible-with-jc/)
|
||||
- [Saltstack command output parsing](https://blog.kellybrazil.com/2020/09/15/parsing-command-output-in-saltstack-with-jc/)
|
||||
- [Nornir command output parsing](https://blog.kellybrazil.com/2020/12/09/parsing-command-output-in-nornir-with-jc/)
|
||||
|
||||
## Installation
|
||||
There are several ways to get `jc`. You can install via `pip`; other OS package repositories like `apt-get`, `dnf`, `zypper`, `pacman`, `nix-env`, `guix`, `brew`, or `portsnap`; via DEB/RPM packages; or by downloading the correct binary for your architecture and running it anywhere on your filesystem.
|
||||
There are several ways to get `jc`. You can install via `pip`; other OS package repositories like `apt-get`, `dnf`, `zypper`, `pacman`, `nix-env`, `guix`, `brew`, or `portsnap`; via DEB/RPM packaged binaries; or by downloading the correct binary for your architecture and running it anywhere on your filesystem.
|
||||
|
||||
### Pip (macOS, linux, unix, Windows)
|
||||
```bash
|
||||
@@ -102,7 +104,7 @@ pip3 install jc
|
||||
| FreeBSD | `portsnap fetch update && cd /usr/ports/textproc/py-jc && make install clean` |
|
||||
| Ansible filter plugin | `ansible-galaxy collection install community.general` |
|
||||
|
||||
> For more packages and binaries, see https://kellyjonbrazil.github.io/jc-packaging/.
|
||||
> For more packages and binaries, see the [jc packaging](https://kellyjonbrazil.github.io/jc-packaging/) site.
|
||||
|
||||
## Usage
|
||||
`jc` accepts piped input from `STDIN` and outputs a JSON representation of the previous command's output to `STDOUT`.
|
||||
@@ -115,8 +117,6 @@ jc [OPTIONS] COMMAND
|
||||
```
|
||||
The JSON output can be compact (default) or pretty formatted with the `-p` option.
|
||||
|
||||
> Note: For best results set the `LANG` locale environment variable to `C`. For example, either by setting directly on the command-line: `$ LANG=C date | jc --date`, or by exporting to the environment before running commands: `$ export LANG=C`.
|
||||
|
||||
### Parsers
|
||||
|
||||
- `--acpi` enables the `acpi` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/acpi))
|
||||
@@ -182,6 +182,8 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio
|
||||
- `--timedatectl` enables the `timedatectl status` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/timedatectl))
|
||||
- `--tracepath` enables the `tracepath` and `tracepath6` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/tracepath))
|
||||
- `--traceroute` enables the `traceroute` and `traceroute6` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/traceroute))
|
||||
- `--ufw` enables the `ufw status` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw))
|
||||
- `--ufw-appinfo` enables the `ufw app info [application]` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw_appinfo))
|
||||
- `--uname` enables the `uname -a` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/uname))
|
||||
- `--upower` enables the `upower` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/upower))
|
||||
- `--uptime` enables the `uptime` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/uptime))
|
||||
@@ -201,6 +203,19 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio
|
||||
- `-r` raw output. Provides a more literal JSON output, typically with string values and no additional semantic processing
|
||||
- `-v` version information
|
||||
|
||||
### Exit Codes
|
||||
Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to determine if an error was from the parsed program or `jc`.
|
||||
|
||||
Consider the following examples using `ifconfig`:
|
||||
|
||||
| `ifconfig` exit code | `jc` exit code | Combined exit code | Interpretation |
|
||||
|----------------------|----------------|--------------------|------------------------------------|
|
||||
| `0` | `0` | `0` | No errors |
|
||||
| `1` | `0` | `1` | Error in `ifconfig` |
|
||||
| `0` | `100` | `100` | Error in `jc` |
|
||||
| `1` | `100` | `101` | Error in both `ifconfig` and `jc` |
|
||||
|
||||
|
||||
### Setting Custom Colors via Environment Variable
|
||||
You can specify custom colors via the `JC_COLORS` environment variable. The `JC_COLORS` environment variable takes four comma separated string values in the following format:
|
||||
```bash
|
||||
@@ -226,12 +241,30 @@ Custom local parser plugins may be placed in a `jc/jcparsers` folder in your loc
|
||||
|
||||
Local parser plugins are standard python module files. Use the [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) parser as a template and simply place a `.py` file in the `jcparsers` subfolder.
|
||||
|
||||
Local plugin filenames must be valid python module names, therefore must consist entirely of alphanumerics and start with a letter. Local plugins may override default plugins.
|
||||
Local plugin filenames must be valid python module names, therefore must consist entirely of alphanumerics and start with a letter. Local plugins may override default parsers.
|
||||
|
||||
> Note: The application data directory follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)
|
||||
|
||||
### Caveats
|
||||
**Locale:**
|
||||
|
||||
For best results set the `LANG` locale environment variable to `C`. For example, either by setting directly on the command-line:
|
||||
```
|
||||
$ LANG=C date | jc --date
|
||||
```
|
||||
or by exporting to the environment before running commands:
|
||||
```
|
||||
$ export LANG=C
|
||||
```
|
||||
|
||||
**Timezones:**
|
||||
|
||||
Some parsers have calculated epoch timestamp fields added to the output. Unless a timestamp field name has a `_utc` suffix it is considered naive. (i.e. based on the local timezone of the system the `jc` parser was run on).
|
||||
|
||||
If a UTC timezone can be detected in the text of the command output, the timestamp will be timezone aware and have a `_utc` suffix on the key name. (e.g. `epoch_utc`) No other timezones are supported for aware timestamps.
|
||||
|
||||
## Compatibility
|
||||
Some parsers like `ls`, `ps`, `dig`, etc. will work on any platform. Other parsers that are platform-specific will generate a warning message if they are used on an unsupported platform. To see all parser information, including compatibility, run `jc -ap`.
|
||||
Some parsers like `dig`, `xml`, `csv`, etc. will work on any platform. Other parsers that convert platform-specific output will generate a warning message if they are run on an unsupported platform. To see all parser information, including compatibility, run `jc -ap`.
|
||||
|
||||
You may still use a parser on an unsupported platform - for example, you may want to parse a file with linux `lsof` output on an macOS laptop. In that case you can suppress the warning message with the `-q` cli option or the `quiet=True` function parameter in `parse()`:
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
# jc.parsers.dig
|
||||
jc - JSON CLI output utility `dig` command output parser
|
||||
|
||||
Options supported:
|
||||
- `+noall +answer` options are supported in cases where only the answer information is desired.
|
||||
- `+axfr` option is supported on its own
|
||||
|
||||
The `when_epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on)
|
||||
|
||||
The `when_epoch_utc` calculated timestamp field is timezone-aware and is only available if the timezone field is UTC.
|
||||
@@ -274,6 +278,42 @@ Examples:
|
||||
}
|
||||
]
|
||||
|
||||
$ dig +noall +answer cnn.com | jc --dig -p
|
||||
[
|
||||
{
|
||||
"answer": [
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.193.67"
|
||||
},
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.65.67"
|
||||
},
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.1.67"
|
||||
},
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.129.67"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
## info
|
||||
```python
|
||||
@@ -299,6 +339,6 @@ Returns:
|
||||
List of Dictionaries. Raw or processed structured data.
|
||||
|
||||
## Parser Information
|
||||
Compatibility: linux, aix, freebsd, darwin
|
||||
Compatibility: linux, aix, freebsd, darwin, win32, cygwin
|
||||
|
||||
Version 2.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
Version 2.1 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
|
||||
@@ -9,16 +9,14 @@ Options supported:
|
||||
- `/C, /-C`
|
||||
- `/S`
|
||||
|
||||
The "Magic" syntax is not supported since the `dir` command is a shell builtin.
|
||||
|
||||
The `epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on)
|
||||
|
||||
Usage (cli):
|
||||
|
||||
C:> dir | jc --dir
|
||||
|
||||
or
|
||||
|
||||
C:> jc dir
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.dir
|
||||
@@ -145,4 +143,4 @@ Returns:
|
||||
## Parser Information
|
||||
Compatibility: win32
|
||||
|
||||
Version 1.2 by Rasheed Elsaleh (rasheed@rebelliondefense.com)
|
||||
Version 1.3 by Rasheed Elsaleh (rasheed@rebelliondefense.com)
|
||||
|
||||
@@ -5,6 +5,8 @@ jc - JSON CLI output utility `history` command output parser
|
||||
|
||||
This parser will output a list of dictionaries each containing `line` and `command` keys. If you would like a simple dictionary output, then use the `-r` command-line option or the `raw=True` argument in the `parse()` function.
|
||||
|
||||
The "Magic" syntax is not supported since the `history` command is a shell builtin.
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ history | jc --history
|
||||
|
||||
@@ -5,14 +5,12 @@ jc - JSON CLI output utility `jobs` command output parser
|
||||
|
||||
Also supports the `-l` option.
|
||||
|
||||
The "Magic" syntax is not supported since the `jobs` command is a shell builtin.
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ jobs | jc --jobs
|
||||
|
||||
or
|
||||
|
||||
$ jc jobs
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.jobs
|
||||
|
||||
@@ -38,14 +38,26 @@ Schema:
|
||||
"round_trip_ms_stddev": float,
|
||||
"responses": [
|
||||
{
|
||||
"type": string, # ('reply' or 'timeout')
|
||||
"type": string, # 'reply', 'timeout', 'unparsable_line', etc. See `_error_type.type_map` for all options
|
||||
"unparsed_line": string, # only if an 'unparsable_line' type
|
||||
"timestamp": float,
|
||||
"bytes": integer,
|
||||
"response_ip": string,
|
||||
"icmp_seq": integer,
|
||||
"ttl": integer,
|
||||
"time_ms": float,
|
||||
"duplicate": boolean
|
||||
"duplicate": boolean,
|
||||
"vr": integer, # hex value converted to decimal
|
||||
"hl": integer, # hex value converted to decimal
|
||||
"tos": integer, # hex value converted to decimal
|
||||
"len": integer, # hex value converted to decimal
|
||||
"id": integer, # hex value converted to decimal
|
||||
"flg": integer, # hex value converted to decimal
|
||||
"off": integer, # hex value converted to decimal
|
||||
"pro": integer, # hex value converted to decimal
|
||||
"cks": ingeger, # hex value converted to decimal
|
||||
"src": string,
|
||||
"dst": string
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -97,7 +109,6 @@ Examples:
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
$ ping -c 3 -p ff cnn.com | jc --ping -p -r
|
||||
{
|
||||
"destination_ip": "151.101.129.67",
|
||||
@@ -170,4 +181,4 @@ Returns:
|
||||
## Parser Information
|
||||
Compatibility: linux, darwin, freebsd
|
||||
|
||||
Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
Version 1.5 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
|
||||
225
docs/parsers/ufw.md
Normal file
225
docs/parsers/ufw.md
Normal file
@@ -0,0 +1,225 @@
|
||||
[Home](https://kellyjonbrazil.github.io/jc/)
|
||||
|
||||
# jc.parsers.ufw
|
||||
jc - JSON CLI output utility `ufw status` command output parser
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ ufw status | jc --ufw
|
||||
|
||||
or
|
||||
|
||||
$ jc ufw status
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.ufw
|
||||
result = jc.parsers.ufw.parse(ufw_command_output)
|
||||
|
||||
Schema:
|
||||
|
||||
{
|
||||
"status": string,
|
||||
"logging": string,
|
||||
"logging_level": string,
|
||||
"default": string,
|
||||
"new_profiles": string,
|
||||
"rules": [
|
||||
{
|
||||
"action": string,
|
||||
"action_direction": string, # null if blank
|
||||
"index": integer, # null if blank
|
||||
"network_protocol": string,
|
||||
"to_ip": string,
|
||||
"to_ip_prefix": integer,
|
||||
"to_interface": string,
|
||||
"to_transport": string,
|
||||
"to_ports": [
|
||||
integer
|
||||
],
|
||||
"to_port_ranges": [
|
||||
{
|
||||
"start": integer,
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"to_service": string, # null if any to ports or port_ranges are set
|
||||
"from_ip": string,
|
||||
"from_ip_prefix": integer,
|
||||
"from_interface": string,
|
||||
"from_transport": string,
|
||||
"from_ports": [
|
||||
integer
|
||||
],
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": integer,
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"from_service": string, # null if any from ports or port_ranges are set
|
||||
"comment": string # null if no comment
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Examples:
|
||||
|
||||
$ ufw status verbose | jc --ufw -p
|
||||
{
|
||||
"status": "active",
|
||||
"logging": "on",
|
||||
"logging_level": "low",
|
||||
"default": "deny (incoming), allow (outgoing), disabled (routed)",
|
||||
"new_profiles": "skip",
|
||||
"rules": [
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "any",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
22
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": 0,
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": 0,
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 65535
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "tcp",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
80,
|
||||
443
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": 0,
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": 0,
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 65535
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
$ ufw status verbose | jc --ufw -p -r
|
||||
{
|
||||
"status": "active",
|
||||
"logging": "on",
|
||||
"logging_level": "low",
|
||||
"default": "deny (incoming), allow (outgoing), disabled (routed)",
|
||||
"new_profiles": "skip",
|
||||
"rules": [
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "any",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
"22"
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": "0",
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": "0",
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": "0",
|
||||
"end": "65535"
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "tcp",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
"80",
|
||||
"443"
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": "0",
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": "0",
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": "0",
|
||||
"end": "65535"
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## info
|
||||
```python
|
||||
info()
|
||||
```
|
||||
Provides parser metadata (version, author, etc.)
|
||||
|
||||
## parse
|
||||
```python
|
||||
parse(data, raw=False, quiet=False)
|
||||
```
|
||||
|
||||
Main text parsing function
|
||||
|
||||
Parameters:
|
||||
|
||||
data: (string) text data to parse
|
||||
raw: (boolean) output preprocessed JSON if True
|
||||
quiet: (boolean) suppress warning messages if True
|
||||
|
||||
Returns:
|
||||
|
||||
Dictionary. Raw or processed structured data.
|
||||
|
||||
## Parser Information
|
||||
Compatibility: linux
|
||||
|
||||
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
158
docs/parsers/ufw_appinfo.md
Normal file
158
docs/parsers/ufw_appinfo.md
Normal file
@@ -0,0 +1,158 @@
|
||||
[Home](https://kellyjonbrazil.github.io/jc/)
|
||||
|
||||
# jc.parsers.ufw_appinfo
|
||||
jc - JSON CLI output utility `ufw app info [application]` command output parser
|
||||
|
||||
Supports individual apps via `ufw app info [application]` and all apps list via `ufw app info all`.
|
||||
|
||||
Because `ufw` application definitions allow overlapping ports and port ranges, this parser preserves that behavior, but also provides `normalized` lists and ranges that remove duplicate ports and merge overlapping ranges.
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ ufw app info OpenSSH | jc --ufw-appinfo
|
||||
|
||||
or
|
||||
|
||||
$ jc ufw app info OpenSSH
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.ufw_appinfo
|
||||
result = jc.parsers.ufw_appinfo.parse(ufw_appinfo_command_output)
|
||||
|
||||
Schema:
|
||||
|
||||
[
|
||||
{
|
||||
"profile": string,
|
||||
"title": string,
|
||||
"description": string,
|
||||
"tcp_list": [
|
||||
integer
|
||||
],
|
||||
"tcp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"udp_list": [
|
||||
integer
|
||||
],
|
||||
"udp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"normalized_tcp_list": [
|
||||
integers # duplicates and overlapping are removed
|
||||
],
|
||||
"normalized_tcp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integers # overlapping are merged
|
||||
}
|
||||
],
|
||||
"normalized_udp_list": [
|
||||
integers # duplicates and overlapping are removed
|
||||
],
|
||||
"normalized_udp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integers # overlapping are merged
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Examples:
|
||||
|
||||
$ ufw app info MSN | jc --ufw-appinfo -p
|
||||
[
|
||||
{
|
||||
"profile": "MSN",
|
||||
"title": "MSN Chat",
|
||||
"description": "MSN chat protocol (with file transfer and voice)",
|
||||
"tcp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"udp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"tcp_ranges": [
|
||||
{
|
||||
"start": 6891,
|
||||
"end": 6900
|
||||
}
|
||||
],
|
||||
"normalized_tcp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"normalized_tcp_ranges": [
|
||||
{
|
||||
"start": 6891,
|
||||
"end": 6900
|
||||
}
|
||||
],
|
||||
"normalized_udp_list": [
|
||||
1863,
|
||||
6901
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
$ ufw app info MSN | jc --ufw-appinfo -p -r
|
||||
[
|
||||
{
|
||||
"profile": "MSN",
|
||||
"title": "MSN Chat",
|
||||
"description": "MSN chat protocol (with file transfer and voice)",
|
||||
"tcp_list": [
|
||||
"1863",
|
||||
"6901"
|
||||
],
|
||||
"udp_list": [
|
||||
"1863",
|
||||
"6901"
|
||||
],
|
||||
"tcp_ranges": [
|
||||
{
|
||||
"start": "6891",
|
||||
"end": "6900"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
## info
|
||||
```python
|
||||
info()
|
||||
```
|
||||
Provides parser metadata (version, author, etc.)
|
||||
|
||||
## parse
|
||||
```python
|
||||
parse(data, raw=False, quiet=False)
|
||||
```
|
||||
|
||||
Main text parsing function
|
||||
|
||||
Parameters:
|
||||
|
||||
data: (string) text data to parse
|
||||
raw: (boolean) output preprocessed JSON if True
|
||||
quiet: (boolean) suppress warning messages if True
|
||||
|
||||
Returns:
|
||||
|
||||
List of Dictionaries. Raw or processed structured data.
|
||||
|
||||
## Parser Information
|
||||
Compatibility: linux
|
||||
|
||||
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
@@ -74,7 +74,7 @@ Returns:
|
||||
convert_to_int(value)
|
||||
```
|
||||
|
||||
Converts string input to integer by stripping all non-numeric characters
|
||||
Converts string and float input to int. Strips all non-numeric characters from strings.
|
||||
|
||||
Parameters:
|
||||
|
||||
@@ -90,7 +90,7 @@ Returns:
|
||||
convert_to_float(value)
|
||||
```
|
||||
|
||||
Converts string input to float by stripping all non-numeric characters
|
||||
Converts string and int input to float. Strips all non-numeric characters from strings.
|
||||
|
||||
Parameters:
|
||||
|
||||
|
||||
@@ -62,15 +62,15 @@ Module Example:
|
||||
... ;; Got answer:
|
||||
... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64612
|
||||
... ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
|
||||
...
|
||||
...
|
||||
... ;; OPT PSEUDOSECTION:
|
||||
... ; EDNS: version: 0, flags:; udp: 4096
|
||||
... ;; QUESTION SECTION:
|
||||
... ;example.com. IN A
|
||||
...
|
||||
...
|
||||
... ;; ANSWER SECTION:
|
||||
... example.com. 29658 IN A 93.184.216.34
|
||||
...
|
||||
...
|
||||
... ;; Query time: 52 msec
|
||||
... ;; SERVER: 2600:1700:bab0:d40::1#53(2600:1700:bab0:d40::1)
|
||||
... ;; WHEN: Fri Apr 16 16:13:00 PDT 2021
|
||||
@@ -86,4 +86,4 @@ Module Example:
|
||||
"""
|
||||
|
||||
name = 'jc'
|
||||
__version__ = '1.15.2'
|
||||
__version__ = '1.15.4'
|
||||
|
||||
238
jc/cli.py
238
jc/cli.py
@@ -10,9 +10,13 @@ import shlex
|
||||
import importlib
|
||||
import textwrap
|
||||
import signal
|
||||
import subprocess
|
||||
import json
|
||||
import jc
|
||||
import jc.appdirs as appdirs
|
||||
import jc.utils
|
||||
import jc.tracebackplus
|
||||
|
||||
# make pygments import optional
|
||||
try:
|
||||
import pygments
|
||||
@@ -102,6 +106,8 @@ parsers = [
|
||||
'timedatectl',
|
||||
'tracepath',
|
||||
'traceroute',
|
||||
'ufw',
|
||||
'ufw-appinfo',
|
||||
'uname',
|
||||
'upower',
|
||||
'uptime',
|
||||
@@ -112,6 +118,9 @@ parsers = [
|
||||
'yaml'
|
||||
]
|
||||
|
||||
JC_ERROR_EXIT = 100
|
||||
|
||||
|
||||
# List of custom or override parsers.
|
||||
# Allow any <user_data_dir>/jc/jcparsers/*.py
|
||||
local_parsers = []
|
||||
@@ -184,10 +193,9 @@ def set_env_colors(env_colors=None):
|
||||
|
||||
Default colors:
|
||||
|
||||
JC_COLORS=blue,brightblack,magenta,green
|
||||
JC_COLORS=blue,brightblack,magenta,green
|
||||
or
|
||||
JC_COLORS=default,default,default,default
|
||||
|
||||
JC_COLORS=default,default,default,default
|
||||
"""
|
||||
input_error = False
|
||||
|
||||
@@ -205,7 +213,7 @@ def set_env_colors(env_colors=None):
|
||||
|
||||
# if there is an issue with the env variable, just set all colors to default and move on
|
||||
if input_error:
|
||||
jc.utils.warning_message('could not parse JC_COLORS environment variable')
|
||||
jc.utils.warning_message('Could not parse JC_COLORS environment variable')
|
||||
color_list = ['default', 'default', 'default', 'default']
|
||||
|
||||
# Try the color set in the JC_COLORS env variable first. If it is set to default, then fall back to default colors
|
||||
@@ -219,15 +227,12 @@ def set_env_colors(env_colors=None):
|
||||
|
||||
def piped_output():
|
||||
"""Return False if stdout is a TTY. True if output is being piped to another program"""
|
||||
if sys.stdout.isatty():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return False if sys.stdout.isatty() else True
|
||||
|
||||
|
||||
def ctrlc(signum, frame):
|
||||
"""Exit with error on SIGINT"""
|
||||
sys.exit(1)
|
||||
sys.exit(JC_ERROR_EXIT)
|
||||
|
||||
|
||||
def parser_shortname(parser_argument):
|
||||
@@ -375,33 +380,38 @@ def versiontext():
|
||||
|
||||
def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False):
|
||||
"""Return a JSON formatted string. String may include color codes or be pretty printed."""
|
||||
separators = (',', ':')
|
||||
indent = None
|
||||
|
||||
if pretty:
|
||||
separators = None
|
||||
indent = 2
|
||||
|
||||
if not mono and not piped_out:
|
||||
# set colors
|
||||
class JcStyle(Style):
|
||||
styles = set_env_colors(env_colors)
|
||||
|
||||
if pretty:
|
||||
return str(highlight(json.dumps(data, indent=2, ensure_ascii=False),
|
||||
JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
|
||||
else:
|
||||
return str(highlight(json.dumps(data, separators=(',', ':'), ensure_ascii=False),
|
||||
JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
|
||||
return str(highlight(json.dumps(data, indent=indent, separators=separators, ensure_ascii=False),
|
||||
JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
|
||||
|
||||
else:
|
||||
if pretty:
|
||||
return json.dumps(data, indent=2, ensure_ascii=False)
|
||||
else:
|
||||
return json.dumps(data, separators=(',', ':'), ensure_ascii=False)
|
||||
return json.dumps(data, indent=indent, separators=separators, ensure_ascii=False)
|
||||
|
||||
|
||||
def generate_magic_command(args):
|
||||
"""
|
||||
Return a tuple with a boolean and a command, where the boolean signifies that
|
||||
the command is valid, and the command is either a command string or None.
|
||||
def magic_parser(args):
|
||||
"""
|
||||
Parse command arguments for magic syntax: jc -p ls -al
|
||||
|
||||
# Parse with magic syntax: jc -p ls -al
|
||||
Return a tuple:
|
||||
valid_command (bool) is this a valid command? (exists in magic dict)
|
||||
run_command (list) list of the user's command to run. None if no command.
|
||||
jc_parser (str) parser to use for this user's command.
|
||||
jc_options (list) list of jc options
|
||||
"""
|
||||
# bail immediately if there are no args or a parser is defined
|
||||
if len(args) <= 1 or args[1].startswith('--'):
|
||||
return False, None
|
||||
return False, None, None, []
|
||||
|
||||
# correctly parse escape characters and spaces with shlex
|
||||
args_given = ' '.join(map(shlex.quote, args[1:])).split()
|
||||
@@ -411,7 +421,7 @@ def generate_magic_command(args):
|
||||
for arg in list(args_given):
|
||||
# parser found - use standard syntax
|
||||
if arg.startswith('--'):
|
||||
return False, None
|
||||
return False, None, None, []
|
||||
|
||||
# option found - populate option list
|
||||
elif arg.startswith('-'):
|
||||
@@ -423,11 +433,11 @@ def generate_magic_command(args):
|
||||
|
||||
# if -h, -a, or -v found in options, then bail out
|
||||
if 'h' in options or 'a' in options or 'v' in options:
|
||||
return False, None
|
||||
return False, None, None, []
|
||||
|
||||
# all options popped and no command found - for case like 'jc -a'
|
||||
# all options popped and no command found - for case like 'jc -x'
|
||||
if len(args_given) == 0:
|
||||
return False, None
|
||||
return False, None, None, []
|
||||
|
||||
magic_dict = {}
|
||||
parser_info = about_jc()['parsers']
|
||||
@@ -444,31 +454,34 @@ def generate_magic_command(args):
|
||||
# try to get a parser for two_word_command, otherwise get one for one_word_command
|
||||
found_parser = magic_dict.get(two_word_command, magic_dict.get(one_word_command))
|
||||
|
||||
# construct a new command line using the standard syntax: COMMAND | jc --PARSER -OPTIONS
|
||||
run_command = ' '.join(args_given)
|
||||
if found_parser:
|
||||
cmd_options = ('-' + ''.join(options)) if options else ''
|
||||
return True, ' '.join([run_command, '|', 'jc', found_parser, cmd_options])
|
||||
else:
|
||||
return False, run_command
|
||||
return (
|
||||
True if found_parser else False, # was a suitable parser found?
|
||||
args_given, # run_command
|
||||
found_parser, # the parser selected
|
||||
options # jc options to preserve
|
||||
)
|
||||
|
||||
|
||||
def magic():
|
||||
"""Runs the command generated by generate_magic_command() to support magic syntax"""
|
||||
valid_command, run_command = generate_magic_command(sys.argv)
|
||||
if valid_command:
|
||||
os.system(run_command)
|
||||
sys.exit(0)
|
||||
elif run_command is None:
|
||||
return
|
||||
else:
|
||||
jc.utils.error_message(f'parser not found for "{run_command}". Use "jc -h" for help.')
|
||||
sys.exit(1)
|
||||
def run_user_command(command):
|
||||
"""Use subprocess to run the user's command. Returns the STDOUT, STDERR, and the Exit Code as a tuple."""
|
||||
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
||||
stdout, stderr = proc.communicate()
|
||||
|
||||
return (
|
||||
stdout or '\n',
|
||||
stderr,
|
||||
proc.returncode
|
||||
)
|
||||
|
||||
|
||||
def combined_exit_code(program_exit=0, jc_exit=0):
|
||||
exit_code = program_exit + jc_exit
|
||||
if exit_code > 255:
|
||||
exit_code = 255
|
||||
return exit_code
|
||||
|
||||
|
||||
def main():
|
||||
import jc.utils
|
||||
|
||||
# break on ctrl-c keyboard interrupt
|
||||
signal.signal(signal.SIGINT, ctrlc)
|
||||
|
||||
@@ -478,17 +491,26 @@ def main():
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# try magic syntax first: e.g. jc -p ls -al
|
||||
magic()
|
||||
# enable colors for Windows cmd.exe terminal
|
||||
if sys.platform.startswith('win32'):
|
||||
os.system('')
|
||||
|
||||
# parse magic syntax first: e.g. jc -p ls -al
|
||||
magic_options = []
|
||||
valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv)
|
||||
|
||||
# set colors
|
||||
jc_colors = os.getenv('JC_COLORS')
|
||||
|
||||
# set options
|
||||
options = []
|
||||
options.extend(magic_options)
|
||||
|
||||
# options
|
||||
for opt in sys.argv:
|
||||
if opt.startswith('-') and not opt.startswith('--'):
|
||||
options.extend(opt[1:])
|
||||
# find options if magic_parser did not find a command
|
||||
if not valid_command:
|
||||
for opt in sys.argv:
|
||||
if opt.startswith('-') and not opt.startswith('--'):
|
||||
options.extend(opt[1:])
|
||||
|
||||
about = 'a' in options
|
||||
debug = 'd' in options
|
||||
@@ -500,6 +522,9 @@ def main():
|
||||
raw = 'r' in options
|
||||
version_info = 'v' in options
|
||||
|
||||
if verbose_debug:
|
||||
jc.tracebackplus.enable(context=11)
|
||||
|
||||
if not pygments_installed:
|
||||
mono = True
|
||||
|
||||
@@ -515,44 +540,91 @@ def main():
|
||||
print(versiontext())
|
||||
sys.exit(0)
|
||||
|
||||
if verbose_debug:
|
||||
import jc.tracebackplus
|
||||
jc.tracebackplus.enable(context=11)
|
||||
# if magic syntax used, try to run the command and error if it's not found, etc.
|
||||
magic_stdout, magic_stderr, magic_exit_code = None, None, 0
|
||||
if run_command:
|
||||
run_command_str = ' '.join(run_command)
|
||||
|
||||
if sys.stdin.isatty():
|
||||
jc.utils.error_message('Missing piped data. Use "jc -h" for help.')
|
||||
sys.exit(1)
|
||||
if valid_command:
|
||||
try:
|
||||
magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command)
|
||||
if magic_stderr:
|
||||
print(magic_stderr[:-1], file=sys.stderr)
|
||||
|
||||
data = sys.stdin.read()
|
||||
except FileNotFoundError:
|
||||
if debug:
|
||||
raise
|
||||
else:
|
||||
jc.utils.error_message(f'"{run_command_str}" command could not be found. For details use the -d or -dd option.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
found = False
|
||||
except Exception:
|
||||
if debug:
|
||||
raise
|
||||
else:
|
||||
jc.utils.error_message(f'"{run_command_str}" command could not be run. For details use the -d or -dd option.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
for arg in sys.argv:
|
||||
parser_name = parser_shortname(arg)
|
||||
elif run_command is not None:
|
||||
jc.utils.error_message(f'"{run_command_str}" cannot be used with Magic syntax. Use "jc -h" for help.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
if parser_name in parsers:
|
||||
# load parser module just in time so we don't need to load all modules
|
||||
parser = parser_module(arg)
|
||||
try:
|
||||
result = parser.parse(data, raw=raw, quiet=quiet)
|
||||
# find the correct parser
|
||||
if magic_found_parser:
|
||||
parser = parser_module(magic_found_parser)
|
||||
parser_name = parser_shortname(magic_found_parser)
|
||||
|
||||
else:
|
||||
found = False
|
||||
for arg in sys.argv:
|
||||
parser_name = parser_shortname(arg)
|
||||
|
||||
if parser_name in parsers:
|
||||
parser = parser_module(arg)
|
||||
found = True
|
||||
break
|
||||
|
||||
except Exception:
|
||||
if debug:
|
||||
raise
|
||||
else:
|
||||
import jc.utils
|
||||
jc.utils.error_message(
|
||||
f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n'
|
||||
' For details use the -d or -dd option. Use "jc -h" for help.')
|
||||
sys.exit(1)
|
||||
if not found:
|
||||
jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
if not found:
|
||||
jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.')
|
||||
sys.exit(1)
|
||||
# check for input errors (pipe vs magic)
|
||||
if not sys.stdin.isatty() and magic_stdout:
|
||||
jc.utils.error_message('Piped data and Magic syntax used simultaneously. Use "jc -h" for help.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output()))
|
||||
elif sys.stdin.isatty() and magic_stdout is None:
|
||||
jc.utils.error_message('Missing piped data. Use "jc -h" for help.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
# parse the data
|
||||
data = magic_stdout or sys.stdin.read()
|
||||
|
||||
try:
|
||||
result = parser.parse(data, raw=raw, quiet=quiet)
|
||||
|
||||
except Exception:
|
||||
if debug:
|
||||
raise
|
||||
else:
|
||||
jc.utils.error_message(
|
||||
f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n'
|
||||
' For details use the -d or -dd option. Use "jc -h" for help.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
# output the json
|
||||
try:
|
||||
print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output()))
|
||||
sys.exit(combined_exit_code(magic_exit_code, 0))
|
||||
|
||||
except Exception:
|
||||
if debug:
|
||||
raise
|
||||
else:
|
||||
jc.utils.error_message(
|
||||
'There was an issue generating the JSON output.\n'
|
||||
' For details use the -d or -dd option.')
|
||||
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
5
jc/exceptions.py
Normal file
5
jc/exceptions.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""jc - JSON CLI output utility exceptions"""
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
BIN
jc/man/jc.1.gz
BIN
jc/man/jc.1.gz
Binary file not shown.
@@ -1,5 +1,9 @@
|
||||
"""jc - JSON CLI output utility `dig` command output parser
|
||||
|
||||
Options supported:
|
||||
- `+noall +answer` options are supported in cases where only the answer information is desired.
|
||||
- `+axfr` option is supported on its own
|
||||
|
||||
The `when_epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on)
|
||||
|
||||
The `when_epoch_utc` calculated timestamp field is timezone-aware and is only available if the timezone field is UTC.
|
||||
@@ -270,19 +274,55 @@ Examples:
|
||||
"rcvd": "78"
|
||||
}
|
||||
]
|
||||
|
||||
$ dig +noall +answer cnn.com | jc --dig -p
|
||||
[
|
||||
{
|
||||
"answer": [
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.193.67"
|
||||
},
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.65.67"
|
||||
},
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.1.67"
|
||||
},
|
||||
{
|
||||
"name": "cnn.com.",
|
||||
"class": "IN",
|
||||
"type": "A",
|
||||
"ttl": 60,
|
||||
"data": "151.101.129.67"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"""
|
||||
import jc.utils
|
||||
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '2.0'
|
||||
version = '2.1'
|
||||
description = '`dig` command parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
|
||||
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
|
||||
compatible = ['linux', 'aix', 'freebsd', 'darwin']
|
||||
compatible = ['linux', 'aix', 'freebsd', 'darwin', 'win32', 'cygwin']
|
||||
magic_commands = ['dig']
|
||||
|
||||
|
||||
@@ -500,6 +540,7 @@ def parse(data, raw=False, quiet=False):
|
||||
# section can be: header, flags, question, authority, answer, axfr, additional, opt_pseudosection, footer
|
||||
section = ''
|
||||
output_entry = {}
|
||||
answer_list = []
|
||||
|
||||
if jc.utils.has_data(data):
|
||||
for line in cleandata:
|
||||
@@ -581,7 +622,12 @@ def parse(data, raw=False, quiet=False):
|
||||
output_entry.update({'authority': authority_list})
|
||||
continue
|
||||
|
||||
if not line.startswith(';') and section == 'answer':
|
||||
# https://github.com/kellyjonbrazil/jc/issues/133
|
||||
# to allow parsing of output that only has the answer section - e.g:
|
||||
# dig +noall +answer example.com
|
||||
# we allow section to be 'answer' (normal output) or
|
||||
# '', which means +noall +answer was used.
|
||||
if not line.startswith(';') and (section == 'answer' or section == ''):
|
||||
answer_list.append(_parse_answer(line))
|
||||
output_entry.update({'answer': answer_list})
|
||||
continue
|
||||
|
||||
@@ -6,16 +6,14 @@ Options supported:
|
||||
- `/C, /-C`
|
||||
- `/S`
|
||||
|
||||
The "Magic" syntax is not supported since the `dir` command is a shell builtin.
|
||||
|
||||
The `epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on)
|
||||
|
||||
Usage (cli):
|
||||
|
||||
C:> dir | jc --dir
|
||||
|
||||
or
|
||||
|
||||
C:> jc dir
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.dir
|
||||
@@ -121,14 +119,13 @@ import jc.utils
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.2'
|
||||
version = '1.3'
|
||||
description = '`dir` command parser'
|
||||
author = 'Rasheed Elsaleh'
|
||||
author_email = 'rasheed@rebelliondefense.com'
|
||||
|
||||
# compatible options: win32
|
||||
compatible = ['win32']
|
||||
magic_commands = ['dir']
|
||||
|
||||
|
||||
__version__ = info.version
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This parser will output a list of dictionaries each containing `line` and `command` keys. If you would like a simple dictionary output, then use the `-r` command-line option or the `raw=True` argument in the `parse()` function.
|
||||
|
||||
The "Magic" syntax is not supported since the `history` command is a shell builtin.
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ history | jc --history
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
Also supports the `-l` option.
|
||||
|
||||
The "Magic" syntax is not supported since the `jobs` command is a shell builtin.
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ jobs | jc --jobs
|
||||
|
||||
or
|
||||
|
||||
$ jc jobs
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.jobs
|
||||
|
||||
@@ -35,14 +35,26 @@ Schema:
|
||||
"round_trip_ms_stddev": float,
|
||||
"responses": [
|
||||
{
|
||||
"type": string, # ('reply' or 'timeout')
|
||||
"type": string, # 'reply', 'timeout', 'unparsable_line', etc. See `_error_type.type_map` for all options
|
||||
"unparsed_line": string, # only if an 'unparsable_line' type
|
||||
"timestamp": float,
|
||||
"bytes": integer,
|
||||
"response_ip": string,
|
||||
"icmp_seq": integer,
|
||||
"ttl": integer,
|
||||
"time_ms": float,
|
||||
"duplicate": boolean
|
||||
"duplicate": boolean,
|
||||
"vr": integer, # hex value converted to decimal
|
||||
"hl": integer, # hex value converted to decimal
|
||||
"tos": integer, # hex value converted to decimal
|
||||
"len": integer, # hex value converted to decimal
|
||||
"id": integer, # hex value converted to decimal
|
||||
"flg": integer, # hex value converted to decimal
|
||||
"off": integer, # hex value converted to decimal
|
||||
"pro": integer, # hex value converted to decimal
|
||||
"cks": ingeger, # hex value converted to decimal
|
||||
"src": string,
|
||||
"dst": string
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -94,7 +106,6 @@ Examples:
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
$ ping -c 3 -p ff cnn.com | jc --ping -p -r
|
||||
{
|
||||
"destination_ip": "151.101.129.67",
|
||||
@@ -141,12 +152,13 @@ Examples:
|
||||
}
|
||||
"""
|
||||
import string
|
||||
import ipaddress
|
||||
import jc.utils
|
||||
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.4'
|
||||
version = '1.5'
|
||||
description = '`ping` and `ping6` command parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
@@ -171,7 +183,8 @@ def _process(proc_data):
|
||||
|
||||
Dictionary. Structured data to conform to the schema.
|
||||
"""
|
||||
int_list = ['data_bytes', 'packets_transmitted', 'packets_received', 'bytes', 'icmp_seq', 'ttl', 'duplicates']
|
||||
int_list = ['data_bytes', 'packets_transmitted', 'packets_received', 'bytes', 'icmp_seq', 'ttl',
|
||||
'duplicates', 'vr', 'hl', 'tos', 'len', 'id', 'flg', 'off', 'pro', 'cks']
|
||||
float_list = ['packet_loss_percent', 'round_trip_ms_min', 'round_trip_ms_avg', 'round_trip_ms_max',
|
||||
'round_trip_ms_stddev', 'timestamp', 'time_ms']
|
||||
|
||||
@@ -193,6 +206,54 @@ def _process(proc_data):
|
||||
return proc_data
|
||||
|
||||
|
||||
def _ipv6_in(line):
|
||||
line_list = line.replace('(', ' ').replace(')', ' ').replace(',', ' ').replace('%', ' ').split()
|
||||
ipv6 = False
|
||||
for item in line_list:
|
||||
try:
|
||||
_ = ipaddress.IPv6Address(item)
|
||||
ipv6 = True
|
||||
except Exception:
|
||||
pass
|
||||
return ipv6
|
||||
|
||||
|
||||
def _error_type(line):
|
||||
# from https://github.com/dgibson/iputils/blob/master/ping.c
|
||||
# https://android.googlesource.com/platform/external/ping/+/8fc3c91cf9e7f87bc20b9e6d3ea2982d87b70d9a/ping.c
|
||||
# https://opensource.apple.com/source/network_cmds/network_cmds-328/ping.tproj/ping.c
|
||||
type_map = {
|
||||
'Destination Net Unreachable': 'destination_net_unreachable',
|
||||
'Destination Host Unreachable': 'destination_host_unreachable',
|
||||
'Destination Protocol Unreachable': 'destination_protocol_unreachable',
|
||||
'Destination Port Unreachable': 'destination_port_unreachable',
|
||||
'Frag needed and DF set': 'frag_needed_and_df_set',
|
||||
'Source Route Failed': 'source_route_failed',
|
||||
'Destination Net Unknown': 'destination_net_unknown',
|
||||
'Destination Host Unknown': 'destination_host_unknown',
|
||||
'Source Host Isolated': 'source_host_isolated',
|
||||
'Destination Net Prohibited': 'destination_net_prohibited',
|
||||
'Destination Host Prohibited': 'destination_host_prohibited',
|
||||
'Destination Net Unreachable for Type of Service': 'destination_net_unreachable_for_type_of_service',
|
||||
'Destination Host Unreachable for Type of Service': 'destination_host_unreachable_for_type_of_service',
|
||||
'Packet filtered': 'packet_filtered',
|
||||
'Precedence Violation': 'precedence_violation',
|
||||
'Precedence Cutoff': 'precedence_cutoff',
|
||||
'Dest Unreachable, Bad Code': 'dest_unreachable_bad_code',
|
||||
'Redirect Network': 'redirect_network',
|
||||
'Redirect Host': 'redirect_host',
|
||||
'Redirect Type of Service and Network': 'redirect_type_of_service_and_network',
|
||||
'Redirect, Bad Code': 'redirect_bad_code',
|
||||
'Time to live exceeded': 'time_to_live_exceeded',
|
||||
'Frag reassembly time exceeded': 'frag_reassembly_time_exceeded',
|
||||
'Time exceeded, Bad Code': 'time_exceeded_bad_code'
|
||||
}
|
||||
|
||||
for err_type, code in type_map.items():
|
||||
if err_type in line:
|
||||
return code
|
||||
|
||||
|
||||
def _linux_parse(data):
|
||||
raw_output = {}
|
||||
ping_responses = []
|
||||
@@ -303,36 +364,41 @@ def _linux_parse(data):
|
||||
continue
|
||||
|
||||
# normal responses
|
||||
else:
|
||||
elif ' bytes from ' in line:
|
||||
try:
|
||||
line = line.replace('(', ' ').replace(')', ' ').replace('=', ' ')
|
||||
|
||||
line = line.replace('(', ' ').replace(')', ' ').replace('=', ' ')
|
||||
# positions of items depend on whether ipv4/ipv6 and/or ip/hostname is used
|
||||
if ipv4 and not hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9)
|
||||
elif ipv4 and hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11)
|
||||
elif not ipv4 and not hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9)
|
||||
elif not ipv4 and hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11)
|
||||
|
||||
# positions of items depend on whether ipv4/ipv6 and/or ip/hostname is used
|
||||
if ipv4 and not hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9)
|
||||
elif ipv4 and hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11)
|
||||
elif not ipv4 and not hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9)
|
||||
elif not ipv4 and hostname:
|
||||
bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11)
|
||||
# if timestamp option is specified, then shift everything right by one
|
||||
timestamp = False
|
||||
if line[0] == '[':
|
||||
timestamp = True
|
||||
bts, rip, iseq, t2l, tms = (bts + 1, rip + 1, iseq + 1, t2l + 1, tms + 1)
|
||||
|
||||
# if timestamp option is specified, then shift everything right by one
|
||||
timestamp = False
|
||||
if line[0] == '[':
|
||||
timestamp = True
|
||||
bts, rip, iseq, t2l, tms = (bts + 1, rip + 1, iseq + 1, t2l + 1, tms + 1)
|
||||
|
||||
response = {
|
||||
'type': 'reply',
|
||||
'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None,
|
||||
'bytes': line.split()[bts],
|
||||
'response_ip': line.split()[rip].rstrip(':'),
|
||||
'icmp_seq': line.split()[iseq],
|
||||
'ttl': line.split()[t2l],
|
||||
'time_ms': line.split()[tms],
|
||||
'duplicate': True if 'DUP!' in line else False
|
||||
}
|
||||
response = {
|
||||
'type': 'reply',
|
||||
'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None,
|
||||
'bytes': line.split()[bts],
|
||||
'response_ip': line.split()[rip].rstrip(':'),
|
||||
'icmp_seq': line.split()[iseq],
|
||||
'ttl': line.split()[t2l],
|
||||
'time_ms': line.split()[tms],
|
||||
'duplicate': True if 'DUP!' in line else False
|
||||
}
|
||||
except Exception:
|
||||
response = {
|
||||
'type': 'unparsable_line',
|
||||
'unparsed_line': line
|
||||
}
|
||||
|
||||
ping_responses.append(response)
|
||||
continue
|
||||
@@ -347,6 +413,7 @@ def _bsd_parse(data):
|
||||
ping_responses = []
|
||||
pattern = None
|
||||
footer = False
|
||||
ping_error = False
|
||||
|
||||
linedata = data.splitlines()
|
||||
|
||||
@@ -420,7 +487,7 @@ def _bsd_parse(data):
|
||||
# ping response lines
|
||||
else:
|
||||
# ipv4 lines
|
||||
if ',' not in line:
|
||||
if not _ipv6_in(line):
|
||||
|
||||
# request timeout
|
||||
if line.startswith('Request timeout for '):
|
||||
@@ -431,9 +498,80 @@ def _bsd_parse(data):
|
||||
ping_responses.append(response)
|
||||
continue
|
||||
|
||||
# catch error responses
|
||||
err = _error_type(line)
|
||||
if err:
|
||||
response = {
|
||||
'type': err
|
||||
}
|
||||
|
||||
try:
|
||||
response['bytes'] = line.split()[0]
|
||||
response['response_ip'] = line.split()[4].strip(':').strip('(').strip(')')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ping_error = True
|
||||
continue
|
||||
|
||||
if ping_error:
|
||||
if line.startswith('Vr'):
|
||||
continue
|
||||
else:
|
||||
error_line = line.split()
|
||||
|
||||
try:
|
||||
response.update(
|
||||
{
|
||||
'vr': int(error_line[0], 16), # convert from hex to decimal
|
||||
'hl': int(error_line[1], 16),
|
||||
'tos': int(error_line[2], 16),
|
||||
'len': int(error_line[3], 16),
|
||||
'id': int(error_line[4], 16),
|
||||
'flg': int(error_line[5], 16),
|
||||
'off': int(error_line[6], 16),
|
||||
'ttl': int(error_line[7], 16),
|
||||
'pro': int(error_line[8], 16),
|
||||
'cks': int(error_line[9], 16),
|
||||
'src': error_line[10],
|
||||
'dst': error_line[11],
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if response:
|
||||
ping_responses.append(response)
|
||||
|
||||
ping_error = False
|
||||
continue
|
||||
|
||||
# normal response
|
||||
else:
|
||||
line = line.replace(':', ' ').replace('=', ' ')
|
||||
elif ' bytes from ' in line:
|
||||
try:
|
||||
line = line.replace(':', ' ').replace('=', ' ')
|
||||
|
||||
response = {
|
||||
'type': 'reply',
|
||||
'bytes': line.split()[0],
|
||||
'response_ip': line.split()[3],
|
||||
'icmp_seq': line.split()[5],
|
||||
'ttl': line.split()[7],
|
||||
'time_ms': line.split()[9]
|
||||
}
|
||||
except Exception:
|
||||
response = {
|
||||
'type': 'unparsable_line',
|
||||
'unparsed_line': line
|
||||
}
|
||||
|
||||
ping_responses.append(response)
|
||||
continue
|
||||
|
||||
# ipv6 lines
|
||||
elif ' bytes from ' in line:
|
||||
try:
|
||||
line = line.replace(',', ' ').replace('=', ' ')
|
||||
response = {
|
||||
'type': 'reply',
|
||||
'bytes': line.split()[0],
|
||||
@@ -442,20 +580,12 @@ def _bsd_parse(data):
|
||||
'ttl': line.split()[7],
|
||||
'time_ms': line.split()[9]
|
||||
}
|
||||
ping_responses.append(response)
|
||||
continue
|
||||
except Exception:
|
||||
response = {
|
||||
'type': 'unparsable_line',
|
||||
'unparsed_line': line
|
||||
}
|
||||
|
||||
# ipv6 lines
|
||||
else:
|
||||
line = line.replace(',', ' ').replace('=', ' ')
|
||||
response = {
|
||||
'type': 'reply',
|
||||
'bytes': line.split()[0],
|
||||
'response_ip': line.split()[3],
|
||||
'icmp_seq': line.split()[5],
|
||||
'ttl': line.split()[7],
|
||||
'time_ms': line.split()[9]
|
||||
}
|
||||
ping_responses.append(response)
|
||||
continue
|
||||
|
||||
@@ -463,8 +593,9 @@ def _bsd_parse(data):
|
||||
if ping_responses:
|
||||
seq_list = []
|
||||
for reply in ping_responses:
|
||||
seq_list.append(reply['icmp_seq'])
|
||||
reply['duplicate'] = True if seq_list.count(reply['icmp_seq']) > 1 else False
|
||||
if 'icmp_seq' in reply:
|
||||
seq_list.append(reply['icmp_seq'])
|
||||
reply['duplicate'] = True if seq_list.count(reply['icmp_seq']) > 1 else False
|
||||
|
||||
raw_output['responses'] = ping_responses
|
||||
|
||||
|
||||
466
jc/parsers/ufw.py
Normal file
466
jc/parsers/ufw.py
Normal file
@@ -0,0 +1,466 @@
|
||||
"""jc - JSON CLI output utility `ufw status` command output parser
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ ufw status | jc --ufw
|
||||
|
||||
or
|
||||
|
||||
$ jc ufw status
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.ufw
|
||||
result = jc.parsers.ufw.parse(ufw_command_output)
|
||||
|
||||
Schema:
|
||||
|
||||
{
|
||||
"status": string,
|
||||
"logging": string,
|
||||
"logging_level": string,
|
||||
"default": string,
|
||||
"new_profiles": string,
|
||||
"rules": [
|
||||
{
|
||||
"action": string,
|
||||
"action_direction": string, # null if blank
|
||||
"index": integer, # null if blank
|
||||
"network_protocol": string,
|
||||
"to_ip": string,
|
||||
"to_ip_prefix": integer,
|
||||
"to_interface": string,
|
||||
"to_transport": string,
|
||||
"to_ports": [
|
||||
integer
|
||||
],
|
||||
"to_port_ranges": [
|
||||
{
|
||||
"start": integer,
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"to_service": string, # null if any to ports or port_ranges are set
|
||||
"from_ip": string,
|
||||
"from_ip_prefix": integer,
|
||||
"from_interface": string,
|
||||
"from_transport": string,
|
||||
"from_ports": [
|
||||
integer
|
||||
],
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": integer,
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"from_service": string, # null if any from ports or port_ranges are set
|
||||
"comment": string # null if no comment
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Examples:
|
||||
|
||||
$ ufw status verbose | jc --ufw -p
|
||||
{
|
||||
"status": "active",
|
||||
"logging": "on",
|
||||
"logging_level": "low",
|
||||
"default": "deny (incoming), allow (outgoing), disabled (routed)",
|
||||
"new_profiles": "skip",
|
||||
"rules": [
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "any",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
22
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": 0,
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": 0,
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 65535
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "tcp",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
80,
|
||||
443
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": 0,
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": 0,
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 65535
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
$ ufw status verbose | jc --ufw -p -r
|
||||
{
|
||||
"status": "active",
|
||||
"logging": "on",
|
||||
"logging_level": "low",
|
||||
"default": "deny (incoming), allow (outgoing), disabled (routed)",
|
||||
"new_profiles": "skip",
|
||||
"rules": [
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "any",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
"22"
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": "0",
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": "0",
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": "0",
|
||||
"end": "65535"
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"action_direction": "IN",
|
||||
"index": null,
|
||||
"network_protocol": "ipv4",
|
||||
"to_interface": "any",
|
||||
"to_transport": "tcp",
|
||||
"to_service": null,
|
||||
"to_ports": [
|
||||
"80",
|
||||
"443"
|
||||
],
|
||||
"to_ip": "0.0.0.0",
|
||||
"to_ip_prefix": "0",
|
||||
"comment": null,
|
||||
"from_ip": "0.0.0.0",
|
||||
"from_ip_prefix": "0",
|
||||
"from_interface": "any",
|
||||
"from_transport": "any",
|
||||
"from_port_ranges": [
|
||||
{
|
||||
"start": "0",
|
||||
"end": "65535"
|
||||
}
|
||||
],
|
||||
"from_service": null
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
"""
|
||||
import jc.utils
|
||||
import re
|
||||
import ipaddress
|
||||
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.0'
|
||||
description = '`ufw status` command parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
compatible = ['linux']
|
||||
magic_commands = ['ufw status']
|
||||
|
||||
|
||||
__version__ = info.version
|
||||
|
||||
|
||||
def _process(proc_data):
|
||||
"""
|
||||
Final processing to conform to the schema.
|
||||
|
||||
Parameters:
|
||||
|
||||
proc_data: (List of Dictionaries) raw structured data to process
|
||||
|
||||
Returns:
|
||||
|
||||
Dictionary. Structured to conform to the schema.
|
||||
"""
|
||||
int_list = ['index', 'to_ip_prefix', 'from_ip_prefix']
|
||||
|
||||
if 'rules' in proc_data:
|
||||
for i, item in enumerate(proc_data['rules']):
|
||||
for key in item:
|
||||
if key in int_list:
|
||||
proc_data['rules'][i][key] = jc.utils.convert_to_int(proc_data['rules'][i][key])
|
||||
|
||||
if key in ['to_ports', 'from_ports']:
|
||||
for i2, item2 in enumerate(proc_data['rules'][i][key]):
|
||||
proc_data['rules'][i][key][i2] = jc.utils.convert_to_int(item2)
|
||||
|
||||
if key in ['to_port_ranges', 'from_port_ranges']:
|
||||
for i2, item2 in enumerate(proc_data['rules'][i][key]):
|
||||
proc_data['rules'][i][key][i2]['start'] = jc.utils.convert_to_int(proc_data['rules'][i][key][i2]['start'])
|
||||
proc_data['rules'][i][key][i2]['end'] = jc.utils.convert_to_int(proc_data['rules'][i][key][i2]['end'])
|
||||
|
||||
return proc_data
|
||||
|
||||
|
||||
def _parse_to_from(linedata, direction, rule_obj=None):
|
||||
|
||||
if rule_obj is None:
|
||||
rule_obj = {}
|
||||
|
||||
# pull out rule index, if they exist: [ 1]
|
||||
if direction == 'to':
|
||||
RE_LINE_NUM = re.compile(r'\[[ 0-9]+\]\s')
|
||||
line_number_match = re.search(RE_LINE_NUM, linedata)
|
||||
if line_number_match:
|
||||
rule_obj['index'] = line_number_match.group(0).replace('[', '').replace(']', '').strip()
|
||||
linedata = re.sub(RE_LINE_NUM, '', linedata)
|
||||
else:
|
||||
rule_obj['index'] = None
|
||||
|
||||
# pull out comments, if they exist
|
||||
if direction == 'from':
|
||||
RE_COMMENT = re.compile(r'#.+$')
|
||||
comment_match = re.search(RE_COMMENT, linedata)
|
||||
if comment_match:
|
||||
rule_obj['comment'] = comment_match.group(0).lstrip('#').strip()
|
||||
linedata = re.sub(RE_COMMENT, '', linedata)
|
||||
else:
|
||||
rule_obj['comment'] = None
|
||||
|
||||
# pull (v6)
|
||||
RE_V6 = re.compile(r'\(v6\)')
|
||||
v6_match = re.search(RE_V6, linedata)
|
||||
if v6_match:
|
||||
rule_obj['network_protocol'] = 'ipv6'
|
||||
linedata = re.sub(RE_V6, '', linedata)
|
||||
elif not rule_obj.get('network_protocol'):
|
||||
rule_obj['network_protocol'] = 'ipv4'
|
||||
|
||||
# pull 'Anywhere' if exists. Assign to 0.0.0.0/0 or ::/0 depending on if (v6) is found
|
||||
if 'Anywhere' in linedata:
|
||||
if rule_obj.get('network_protocol') == 'ipv6':
|
||||
rule_obj[direction + '_ip'] = '::'
|
||||
rule_obj[direction + '_ip_prefix'] = '0'
|
||||
elif rule_obj.get('network_protocol') == 'ipv4':
|
||||
rule_obj[direction + '_ip'] = '0.0.0.0'
|
||||
rule_obj[direction + '_ip_prefix'] = '0'
|
||||
linedata = linedata.replace('Anywhere', '')
|
||||
|
||||
# pull out interface (after 'on')
|
||||
linedata_list = linedata.split(' on ', maxsplit=1)
|
||||
|
||||
if len(linedata_list) > 1:
|
||||
rule_obj[direction + '_interface'] = linedata_list[1].strip()
|
||||
linedata = linedata_list[0]
|
||||
else:
|
||||
rule_obj[direction + '_interface'] = 'any'
|
||||
|
||||
# pull tcp/udp/etc. transport - strip on '/'
|
||||
linedata_list = linedata.rsplit('/', maxsplit=1)
|
||||
if len(linedata_list) > 1:
|
||||
if linedata_list[1].strip() in ['tcp', 'udp', 'ah', 'esp', 'gre', 'ipv6', 'igmp']:
|
||||
rule_obj[direction + '_transport'] = linedata_list[1].strip()
|
||||
linedata = linedata_list[0]
|
||||
else:
|
||||
rule_obj[direction + '_transport'] = 'any'
|
||||
else:
|
||||
rule_obj[direction + '_transport'] = 'any'
|
||||
|
||||
# pull out ipv4 or ipv6 addresses
|
||||
linedata_list = linedata.split()
|
||||
new_linedata_list = []
|
||||
|
||||
valid_ip = None
|
||||
for item in linedata_list:
|
||||
try:
|
||||
valid_ip = ipaddress.IPv4Interface(item)
|
||||
except Exception:
|
||||
try:
|
||||
valid_ip = ipaddress.IPv6Interface(item)
|
||||
except Exception:
|
||||
new_linedata_list.append(item)
|
||||
|
||||
if valid_ip:
|
||||
rule_obj[direction + '_ip'] = str(valid_ip.ip)
|
||||
rule_obj[direction + '_ip_prefix'] = str(valid_ip.with_prefixlen.split('/')[1])
|
||||
linedata = ' '.join(new_linedata_list)
|
||||
|
||||
# find the numeric port(s)
|
||||
linedata_list = linedata.split(',')
|
||||
port_list = []
|
||||
port_ranges = []
|
||||
for item in linedata_list:
|
||||
if item.strip().isnumeric():
|
||||
port_list.append(item.strip())
|
||||
elif ':' in item:
|
||||
p_range = item.strip().split(':', maxsplit=1)
|
||||
port_ranges.append(
|
||||
{
|
||||
"start": p_range[0],
|
||||
"end": p_range[1]
|
||||
}
|
||||
)
|
||||
|
||||
if port_list or port_ranges:
|
||||
rule_obj[direction + '_service'] = None
|
||||
linedata = ''
|
||||
|
||||
if port_list:
|
||||
rule_obj[direction + '_ports'] = port_list
|
||||
|
||||
if port_ranges:
|
||||
rule_obj[direction + '_port_ranges'] = port_ranges
|
||||
|
||||
# only thing left should be the service name.
|
||||
if linedata.strip():
|
||||
rule_obj[direction + '_service'] = linedata.strip()
|
||||
rule_obj[direction + '_transport'] = None
|
||||
|
||||
# check if to/from IP addresses exist. If not, set to 0.0.0.0/0 or ::/0
|
||||
if direction + '_ip' not in rule_obj:
|
||||
if rule_obj.get('network_protocol') == 'ipv6':
|
||||
rule_obj[direction + '_ip'] = '::'
|
||||
rule_obj[direction + '_ip_prefix'] = '0'
|
||||
elif rule_obj.get('network_protocol') == 'ipv4':
|
||||
rule_obj[direction + '_ip'] = '0.0.0.0'
|
||||
rule_obj[direction + '_ip_prefix'] = '0'
|
||||
|
||||
# finally set default ports if no ports exist and there should be some
|
||||
if direction + '_transport' in rule_obj:
|
||||
if rule_obj[direction + '_transport'] in ['tcp', 'udp', 'any']:
|
||||
if not port_list and not port_ranges:
|
||||
rule_obj[direction + '_port_ranges'] = [
|
||||
{
|
||||
'start': '0',
|
||||
'end': '65535'
|
||||
}
|
||||
]
|
||||
rule_obj[direction + '_service'] = None
|
||||
|
||||
return rule_obj
|
||||
|
||||
|
||||
def parse(data, raw=False, quiet=False):
|
||||
"""
|
||||
Main text parsing function
|
||||
|
||||
Parameters:
|
||||
|
||||
data: (string) text data to parse
|
||||
raw: (boolean) output preprocessed JSON if True
|
||||
quiet: (boolean) suppress warning messages if True
|
||||
|
||||
Returns:
|
||||
|
||||
Dictionary. Raw or processed structured data.
|
||||
"""
|
||||
if not quiet:
|
||||
jc.utils.compatibility(__name__, info.compatible)
|
||||
|
||||
raw_output = {}
|
||||
rules_list = []
|
||||
|
||||
if jc.utils.has_data(data):
|
||||
|
||||
rule_lines = False
|
||||
|
||||
for line in filter(None, data.splitlines()):
|
||||
|
||||
if line.startswith('Status: '):
|
||||
raw_output['status'] = line.split(': ', maxsplit=1)[1]
|
||||
continue
|
||||
|
||||
if line.startswith('Logging: '):
|
||||
log_line = line.split(': ', maxsplit=1)
|
||||
log_line = log_line[1]
|
||||
log_line = log_line.split()
|
||||
raw_output['logging'] = log_line[0]
|
||||
if len(log_line) == 2:
|
||||
raw_output['logging_level'] = log_line[1].replace('(', '').replace(')', '').strip()
|
||||
continue
|
||||
|
||||
if line.startswith('Default: '):
|
||||
raw_output['default'] = line.split(': ', maxsplit=1)[1]
|
||||
continue
|
||||
|
||||
if line.startswith('New profiles: '):
|
||||
raw_output['new_profiles'] = line.split(': ', maxsplit=1)[1]
|
||||
continue
|
||||
|
||||
if 'To' in line and 'Action' in line and 'From' in line:
|
||||
rule_lines = True
|
||||
continue
|
||||
|
||||
if rule_lines:
|
||||
if '------' in line:
|
||||
continue
|
||||
|
||||
# Split on action. Left of Action is 'to', right of Action is 'from'
|
||||
rule_obj = {}
|
||||
|
||||
splitline = re.split(r'(ALLOW IN|ALLOW OUT|ALLOW FWD|DENY IN|DENY OUT|DENY FWD|LIMIT IN|LIMIT OUT|LIMIT FWD|REJECT IN|REJECT OUT|REJECT FWD|ALLOW|DENY|LIMIT|REJECT)', line)
|
||||
to_line = splitline[0]
|
||||
action_line = splitline[1]
|
||||
action_list = action_line.split()
|
||||
from_line = splitline[2]
|
||||
action_direction = None
|
||||
|
||||
if len(action_list) == 1:
|
||||
action = action_list[0]
|
||||
elif len(action_list) == 2:
|
||||
action = action_list[0]
|
||||
action_direction = action_list[1]
|
||||
|
||||
rule_obj['action'] = action
|
||||
rule_obj['action_direction'] = action_direction
|
||||
rule_obj.update(_parse_to_from(to_line, 'to'))
|
||||
rule_obj.update(_parse_to_from(from_line, 'from', rule_obj))
|
||||
|
||||
rules_list.append(rule_obj)
|
||||
|
||||
raw_output['rules'] = rules_list
|
||||
|
||||
if raw:
|
||||
return raw_output
|
||||
else:
|
||||
return _process(raw_output)
|
||||
364
jc/parsers/ufw_appinfo.py
Normal file
364
jc/parsers/ufw_appinfo.py
Normal file
@@ -0,0 +1,364 @@
|
||||
"""jc - JSON CLI output utility `ufw app info [application]` command output parser
|
||||
|
||||
Supports individual apps via `ufw app info [application]` and all apps list via `ufw app info all`.
|
||||
|
||||
Because `ufw` application definitions allow overlapping ports and port ranges, this parser preserves that behavior, but also provides `normalized` lists and ranges that remove duplicate ports and merge overlapping ranges.
|
||||
|
||||
Usage (cli):
|
||||
|
||||
$ ufw app info OpenSSH | jc --ufw-appinfo
|
||||
|
||||
or
|
||||
|
||||
$ jc ufw app info OpenSSH
|
||||
|
||||
Usage (module):
|
||||
|
||||
import jc.parsers.ufw_appinfo
|
||||
result = jc.parsers.ufw_appinfo.parse(ufw_appinfo_command_output)
|
||||
|
||||
Schema:
|
||||
|
||||
[
|
||||
{
|
||||
"profile": string,
|
||||
"title": string,
|
||||
"description": string,
|
||||
"tcp_list": [
|
||||
integer
|
||||
],
|
||||
"tcp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"udp_list": [
|
||||
integer
|
||||
],
|
||||
"udp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integer
|
||||
}
|
||||
],
|
||||
"normalized_tcp_list": [
|
||||
integers # duplicates and overlapping are removed
|
||||
],
|
||||
"normalized_tcp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integers # overlapping are merged
|
||||
}
|
||||
],
|
||||
"normalized_udp_list": [
|
||||
integers # duplicates and overlapping are removed
|
||||
],
|
||||
"normalized_udp_ranges": [
|
||||
{
|
||||
"start": integer, # 'any' is converted to start/end: 0/65535
|
||||
"end": integers # overlapping are merged
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Examples:
|
||||
|
||||
$ ufw app info MSN | jc --ufw-appinfo -p
|
||||
[
|
||||
{
|
||||
"profile": "MSN",
|
||||
"title": "MSN Chat",
|
||||
"description": "MSN chat protocol (with file transfer and voice)",
|
||||
"tcp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"udp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"tcp_ranges": [
|
||||
{
|
||||
"start": 6891,
|
||||
"end": 6900
|
||||
}
|
||||
],
|
||||
"normalized_tcp_list": [
|
||||
1863,
|
||||
6901
|
||||
],
|
||||
"normalized_tcp_ranges": [
|
||||
{
|
||||
"start": 6891,
|
||||
"end": 6900
|
||||
}
|
||||
],
|
||||
"normalized_udp_list": [
|
||||
1863,
|
||||
6901
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
$ ufw app info MSN | jc --ufw-appinfo -p -r
|
||||
[
|
||||
{
|
||||
"profile": "MSN",
|
||||
"title": "MSN Chat",
|
||||
"description": "MSN chat protocol (with file transfer and voice)",
|
||||
"tcp_list": [
|
||||
"1863",
|
||||
"6901"
|
||||
],
|
||||
"udp_list": [
|
||||
"1863",
|
||||
"6901"
|
||||
],
|
||||
"tcp_ranges": [
|
||||
{
|
||||
"start": "6891",
|
||||
"end": "6900"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"""
|
||||
import jc.utils
|
||||
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.0'
|
||||
description = '`ufw app info [application]` command parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
compatible = ['linux']
|
||||
magic_commands = ['ufw app']
|
||||
|
||||
|
||||
__version__ = info.version
|
||||
|
||||
|
||||
def _process(proc_data):
|
||||
"""
|
||||
Final processing to conform to the schema.
|
||||
|
||||
Parameters:
|
||||
|
||||
proc_data: (List of Dictionaries) raw structured data to process
|
||||
|
||||
Returns:
|
||||
|
||||
List of Dictionaries. Structured to conform to the schema.
|
||||
"""
|
||||
for profile in proc_data:
|
||||
# convert to ints
|
||||
int_list = ['start', 'end']
|
||||
|
||||
if 'tcp_list' in profile:
|
||||
profile['tcp_list'] = [int(p) for p in profile['tcp_list']]
|
||||
|
||||
if 'udp_list' in profile:
|
||||
profile['udp_list'] = [int(p) for p in profile['udp_list']]
|
||||
|
||||
for protocol in ['tcp', 'udp']:
|
||||
if protocol + '_ranges' in profile:
|
||||
for i, item in enumerate(profile[protocol + '_ranges']):
|
||||
for key in item:
|
||||
if key in int_list:
|
||||
profile[protocol + '_ranges'][i][key] = int(profile[protocol + '_ranges'][i][key])
|
||||
|
||||
# create normalized port lists and port ranges (remove duplicates and merge ranges)
|
||||
# dump ranges into a set of 0 - 65535
|
||||
# if items in the port list are in the set, then remove them
|
||||
# iterate through the set to find gaps and create new ranges based on them
|
||||
for protocol in ['tcp', 'udp']:
|
||||
port_set = set()
|
||||
if protocol + '_ranges' in profile:
|
||||
for item in profile[protocol + '_ranges']:
|
||||
port_set.update(range(item['start'], item['end'] + 1))
|
||||
|
||||
if protocol + '_list' in profile:
|
||||
new_port_list = sorted(set([p for p in profile[protocol + '_list'] if p not in port_set]))
|
||||
if new_port_list:
|
||||
profile['normalized_' + protocol + '_list'] = new_port_list
|
||||
|
||||
new_port_ranges = []
|
||||
state = 'findstart' # 'findstart' or 'findend'
|
||||
for port in range(0, 65535 + 2):
|
||||
if state == 'findstart':
|
||||
port_range_obj = {}
|
||||
if port in port_set:
|
||||
port_range_obj['start'] = port
|
||||
state = 'findend'
|
||||
continue
|
||||
if state == 'findend':
|
||||
if port not in port_set:
|
||||
port_range_obj['end'] = port - 1
|
||||
new_port_ranges.append(port_range_obj)
|
||||
state = 'findstart'
|
||||
|
||||
if new_port_ranges:
|
||||
profile['normalized_' + protocol + '_ranges'] = new_port_ranges
|
||||
|
||||
return proc_data
|
||||
|
||||
|
||||
def _parse_port_list(data, port_list=None):
|
||||
"""return a list of port strings"""
|
||||
# 1,2,3,4,5,6,7,8,9,10,9,30,80:90,8080:8090
|
||||
# overlapping and repeated port numbers are allowed
|
||||
|
||||
if port_list is None:
|
||||
port_list = []
|
||||
|
||||
data = data.split(',')
|
||||
data_list = [p.strip() for p in data if ':' not in p and 'any' not in p]
|
||||
port_list.extend(data_list)
|
||||
|
||||
return port_list
|
||||
|
||||
|
||||
def _parse_port_range(data, range_list=None):
|
||||
"""return a list of dictionaries"""
|
||||
# 1,2,3,4,5,6,7,8,9,10,9,30,80:90,8080:8090
|
||||
# overlapping port ranges are allowed
|
||||
|
||||
if range_list is None:
|
||||
range_list = []
|
||||
|
||||
data = data.strip().split(',')
|
||||
ranges = [p.strip() for p in data if ':' in p]
|
||||
range_obj = {}
|
||||
|
||||
if 'any' in data:
|
||||
range_list.append(
|
||||
{
|
||||
'start': 0,
|
||||
'end': 65535
|
||||
}
|
||||
)
|
||||
|
||||
for range_ in ranges:
|
||||
range_obj = {
|
||||
'start': range_.split(':')[0],
|
||||
'end': range_.split(':')[1]
|
||||
}
|
||||
range_list.append(range_obj)
|
||||
|
||||
return range_list
|
||||
|
||||
|
||||
def parse(data, raw=False, quiet=False):
|
||||
"""
|
||||
Main text parsing function
|
||||
|
||||
Parameters:
|
||||
|
||||
data: (string) text data to parse
|
||||
raw: (boolean) output preprocessed JSON if True
|
||||
quiet: (boolean) suppress warning messages if True
|
||||
|
||||
Returns:
|
||||
|
||||
List of Dictionaries. Raw or processed structured data.
|
||||
"""
|
||||
if not quiet:
|
||||
jc.utils.compatibility(__name__, info.compatible)
|
||||
|
||||
raw_output = []
|
||||
item_obj = {}
|
||||
|
||||
if jc.utils.has_data(data):
|
||||
|
||||
ports = False
|
||||
|
||||
for line in filter(None, data.splitlines()):
|
||||
|
||||
if line.startswith('--'):
|
||||
if item_obj:
|
||||
raw_output.append(item_obj)
|
||||
item_obj = {}
|
||||
continue
|
||||
|
||||
if line.startswith('Profile:'):
|
||||
item_obj['profile'] = line.split(': ')[1]
|
||||
continue
|
||||
|
||||
if line.startswith('Title:'):
|
||||
item_obj['title'] = line.split(': ')[1]
|
||||
continue
|
||||
|
||||
if line.startswith('Description:'):
|
||||
item_obj['description'] = line.split(': ')[1]
|
||||
continue
|
||||
|
||||
if line.startswith('Port'):
|
||||
ports = True
|
||||
continue
|
||||
|
||||
if ports:
|
||||
line_list = line.rsplit('/', maxsplit=1)
|
||||
if len(line_list) == 2:
|
||||
if line_list[1] == 'tcp':
|
||||
tcp_prot_list = _parse_port_list(line_list[0])
|
||||
if tcp_prot_list:
|
||||
item_obj['tcp_list'] = tcp_prot_list
|
||||
|
||||
tcp_prot_range = _parse_port_range(line_list[0])
|
||||
if tcp_prot_range:
|
||||
item_obj['tcp_ranges'] = tcp_prot_range
|
||||
|
||||
elif line_list[1] == 'udp':
|
||||
udp_prot_list = _parse_port_list(line_list[0])
|
||||
if udp_prot_list:
|
||||
item_obj['udp_list'] = udp_prot_list
|
||||
|
||||
udp_prot_range = _parse_port_range(line_list[0])
|
||||
if udp_prot_range:
|
||||
item_obj['udp_ranges'] = udp_prot_range
|
||||
|
||||
# 'any' case
|
||||
else:
|
||||
t_list = []
|
||||
t_range = []
|
||||
u_list = []
|
||||
u_range = []
|
||||
|
||||
if 'tcp_list' in item_obj:
|
||||
t_list = item_obj['tcp_list']
|
||||
|
||||
if 'tcp_ranges' in item_obj:
|
||||
t_range = item_obj['tcp_ranges']
|
||||
|
||||
if 'udp_list' in item_obj:
|
||||
u_list = item_obj['udp_list']
|
||||
|
||||
if 'udp_ranges' in item_obj:
|
||||
u_range = item_obj['udp_ranges']
|
||||
|
||||
t_p_list = _parse_port_list(line, t_list)
|
||||
if t_p_list:
|
||||
item_obj['tcp_list'] = t_p_list
|
||||
|
||||
t_r_list = _parse_port_range(line, t_range)
|
||||
if t_r_list:
|
||||
item_obj['tcp_ranges'] = t_r_list
|
||||
|
||||
u_p_list = _parse_port_list(line, u_list)
|
||||
if u_p_list:
|
||||
item_obj['udp_list'] = u_p_list
|
||||
|
||||
u_r_list = _parse_port_range(line, u_range)
|
||||
if u_r_list:
|
||||
item_obj['udp_ranges'] = u_r_list
|
||||
|
||||
if item_obj:
|
||||
raw_output.append(item_obj)
|
||||
|
||||
if raw:
|
||||
return raw_output
|
||||
else:
|
||||
return _process(raw_output)
|
||||
@@ -43,6 +43,7 @@ Example:
|
||||
}
|
||||
"""
|
||||
import jc.utils
|
||||
from jc.exceptions import ParseError
|
||||
|
||||
|
||||
class info():
|
||||
@@ -60,10 +61,6 @@ class info():
|
||||
__version__ = info.version
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _process(proc_data):
|
||||
"""
|
||||
Final processing to conform to the schema.
|
||||
|
||||
15
jc/utils.py
15
jc/utils.py
@@ -85,7 +85,7 @@ def has_data(data):
|
||||
|
||||
def convert_to_int(value):
|
||||
"""
|
||||
Converts string input to integer by stripping all non-numeric characters
|
||||
Converts string and float input to int. Strips all non-numeric characters from strings.
|
||||
|
||||
Parameters:
|
||||
|
||||
@@ -96,11 +96,12 @@ def convert_to_int(value):
|
||||
integer/None Integer if successful conversion, otherwise None
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
str_val = re.sub(r'[^0-9\-\.]', '', value)
|
||||
try:
|
||||
return int(re.sub(r'[^0-9\-\.]', '', value))
|
||||
except ValueError:
|
||||
return int(str_val)
|
||||
except (ValueError, TypeError):
|
||||
try:
|
||||
return int(convert_to_float(value))
|
||||
return int(float(str_val))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@@ -113,7 +114,7 @@ def convert_to_int(value):
|
||||
|
||||
def convert_to_float(value):
|
||||
"""
|
||||
Converts string input to float by stripping all non-numeric characters
|
||||
Converts string and int input to float. Strips all non-numeric characters from strings.
|
||||
|
||||
Parameters:
|
||||
|
||||
@@ -238,6 +239,9 @@ class timestamp:
|
||||
}
|
||||
utc_tz = False
|
||||
|
||||
# sometimes UTC is referenced as 'Coordinated Universal Time'. Convert to 'UTC'
|
||||
data = data.replace('Coordinated Universal Time', 'UTC')
|
||||
|
||||
if 'UTC' in data:
|
||||
utc_tz = True
|
||||
if 'UTC+' in data or 'UTC-' in data:
|
||||
@@ -253,6 +257,7 @@ class timestamp:
|
||||
{'id': 1500, 'format': '%Y-%m-%d %H:%M', 'locale': None}, # en_US.UTF-8 local format (found in who cli output): 2021-03-23 00:14
|
||||
{'id': 1600, 'format': '%m/%d/%Y %I:%M %p', 'locale': None}, # Windows english format (found in dir cli output): 12/07/2019 02:09 AM
|
||||
{'id': 1700, 'format': '%m/%d/%Y, %I:%M:%S %p', 'locale': None}, # Windows english format wint non-UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC-0600)
|
||||
{'id': 1705, 'format': '%m/%d/%Y, %I:%M:%S %p %Z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC)
|
||||
{'id': 1710, 'format': '%m/%d/%Y, %I:%M:%S %p UTC%z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC+0000)
|
||||
{'id': 2000, 'format': '%a %d %b %Y %I:%M:%S %p %Z', 'locale': None}, # en_US.UTF-8 local format (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM UTC
|
||||
{'id': 3000, 'format': '%a %d %b %Y %I:%M:%S %p', 'locale': None}, # en_US.UTF-8 local format with non-UTC tz (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM IST
|
||||
|
||||
BIN
man/jc.1.gz
BIN
man/jc.1.gz
Binary file not shown.
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
# system should be in "America/Los_Angeles" timezone for all tests to pass
|
||||
|
||||
python3 -m unittest -v
|
||||
|
||||
2
setup.py
2
setup.py
@@ -5,7 +5,7 @@ with open('README.md', 'r') as f:
|
||||
|
||||
setuptools.setup(
|
||||
name='jc',
|
||||
version='1.15.2',
|
||||
version='1.15.4',
|
||||
author='Kelly Brazil',
|
||||
author_email='kellyjonbrazil@gmail.com',
|
||||
description='Converts the output of popular command-line tools and file-types to JSON.',
|
||||
|
||||
@@ -62,6 +62,21 @@ raw JSON output
|
||||
\fB-v\fP
|
||||
version information
|
||||
|
||||
.SH EXIT CODES
|
||||
Any fatal errors within jc will generate an exit code of \fB100\fP, otherwise the exit code will be \fB0\fP. When using the "magic" syntax (e.g. \fBjc ifconfig eth0\fP), jc will store the exit code of the program being parsed and add it to the jc exit code. This way it is easier to determine if an error was from the parsed program or jc.
|
||||
|
||||
Consider the following examples using `ifconfig`:
|
||||
|
||||
.RS
|
||||
ifconfig exit code = \fB0\fP, jc exit code = \fB0\fP, combined exit code = \fB0\fP (no errors)
|
||||
|
||||
ifconfig exit code = \fB1\fP, jc exit code = \fB0\fP, combined exit code = \fB1\fP (error in ifconfig)
|
||||
|
||||
ifconfig exit code = \fB0\fP, jc exit code = \fB100\fP, combined exit code = \fB100\fP (error in jc)
|
||||
|
||||
ifconfig exit code = \fB1\fP, jc exit code = \fB100\fP, combined exit code = \fB101\fP (error in both ifconfig and jc)
|
||||
.RE
|
||||
|
||||
.SH ENVIRONMENT
|
||||
You can specify custom colors via the \fBJC_COLORS\fP environment variable. The \fBJC_COLORS\fP environment variable takes four comma separated string values in the following format:
|
||||
|
||||
@@ -96,6 +111,19 @@ Local plugin filenames must be valid python module names, therefore must consist
|
||||
|
||||
Note: The application data directory follows the XDG Base Directory Specification
|
||||
|
||||
.SH CAVEATS
|
||||
\fBLocale:\fP For best results set the \fBLANG\fP locale environment variable to \fBC\fP. For example, either by setting directly on the command-line:
|
||||
|
||||
\fB$ LANG=C date | jc --date\fP
|
||||
|
||||
or by exporting to the environment before running commands:
|
||||
|
||||
\fB$ export LANG=C\fP
|
||||
|
||||
\fBTimezones:\fP Some parsers have calculated epoch timestamp fields added to the output. Unless a timestamp field name has a \fB_utc\fP suffix it is considered naive. (i.e. based on the local timezone of the system the \fBjc\fP parser was run on).
|
||||
|
||||
If a UTC timezone can be detected in the text of the command output, the timestamp will be timezone aware and have a \fB_utc\fP suffix on the key name. (e.g. \fBepoch_utc\fP) No other timezones are supported for aware timestamps.
|
||||
|
||||
.SH EXAMPLES
|
||||
Standard Syntax:
|
||||
.RS
|
||||
|
||||
@@ -62,26 +62,28 @@ The `jc` parsers can also be used as python modules. In this case the output wil
|
||||
```
|
||||
Two representations of the data are possible. The default representation uses a strict schema per parser and converts known numbers to int/float JSON values. Certain known values of `None` are converted to JSON `null`, known boolean values are converted, and, in some cases, additional semantic context fields are added.
|
||||
|
||||
> Note: Some parsers have calculated epoch timestamp fields added to the output. Unless a timestamp field name has a `_utc` suffix it is considered naive. (i.e. based on the local timezone of the system the `jc` parser was run on).
|
||||
>
|
||||
> If a UTC timezone can be detected in the text of the command output, the timestamp will be timezone aware and have a `_utc` suffix on the key name. (e.g. `epoch_utc`) No other timezones are supported for aware timestamps.
|
||||
|
||||
To access the raw, pre-processed JSON, use the `-r` cli option or the `raw=True` function parameter in `parse()`.
|
||||
|
||||
Schemas for each parser can be found at the documentation link beside each parser below.
|
||||
Schemas for each parser can be found at the documentation link beside each [**Parser**](#parsers) below.
|
||||
|
||||
Release notes can be found [here](https://blog.kellybrazil.com/category/jc-news/).
|
||||
|
||||
## Why Would Anyone Do This!?
|
||||
For more information on the motivations for this project, please see my [blog post](https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/).
|
||||
For more information on the motivations for this project, please see my blog post on [Bringing the Unix Philosophy to the 21st Century](https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/).
|
||||
|
||||
See also:
|
||||
- [libxo on FreeBSD](http://juniper.github.io/libxo/libxo-manual.html)
|
||||
- [powershell](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertto-json?view=powershell-7)
|
||||
- [blog: linux apps should have a json flag](https://thomashunter.name/posts/2012-06-06-linux-cli-apps-should-have-a-json-flag)
|
||||
|
||||
Use Cases:
|
||||
- [Bash scripting](https://blog.kellybrazil.com/2021/04/12/practical-json-at-the-command-line/)
|
||||
- [Ansible command output parsing](https://blog.kellybrazil.com/2020/08/30/parsing-command-output-in-ansible-with-jc/)
|
||||
- [Saltstack command output parsing](https://blog.kellybrazil.com/2020/09/15/parsing-command-output-in-saltstack-with-jc/)
|
||||
- [Nornir command output parsing](https://blog.kellybrazil.com/2020/12/09/parsing-command-output-in-nornir-with-jc/)
|
||||
|
||||
## Installation
|
||||
There are several ways to get `jc`. You can install via `pip`; other OS package repositories like `apt-get`, `dnf`, `zypper`, `pacman`, `nix-env`, `guix`, `brew`, or `portsnap`; via DEB/RPM packages; or by downloading the correct binary for your architecture and running it anywhere on your filesystem.
|
||||
There are several ways to get `jc`. You can install via `pip`; other OS package repositories like `apt-get`, `dnf`, `zypper`, `pacman`, `nix-env`, `guix`, `brew`, or `portsnap`; via DEB/RPM packaged binaries; or by downloading the correct binary for your architecture and running it anywhere on your filesystem.
|
||||
|
||||
### Pip (macOS, linux, unix, Windows)
|
||||
```bash
|
||||
@@ -102,7 +104,7 @@ pip3 install jc
|
||||
| FreeBSD | `portsnap fetch update && cd /usr/ports/textproc/py-jc && make install clean` |
|
||||
| Ansible filter plugin | `ansible-galaxy collection install community.general` |
|
||||
|
||||
> For more packages and binaries, see https://kellyjonbrazil.github.io/jc-packaging/.
|
||||
> For more packages and binaries, see the [jc packaging](https://kellyjonbrazil.github.io/jc-packaging/) site.
|
||||
|
||||
## Usage
|
||||
`jc` accepts piped input from `STDIN` and outputs a JSON representation of the previous command's output to `STDOUT`.
|
||||
@@ -115,8 +117,6 @@ jc [OPTIONS] COMMAND
|
||||
```
|
||||
The JSON output can be compact (default) or pretty formatted with the `-p` option.
|
||||
|
||||
> Note: For best results set the `LANG` locale environment variable to `C`. For example, either by setting directly on the command-line: `$ LANG=C date | jc --date`, or by exporting to the environment before running commands: `$ export LANG=C`.
|
||||
|
||||
### Parsers
|
||||
{% for parser in jc.parsers %}
|
||||
- `{{ parser.argument }}` enables the {{ parser.description }} ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/{{ parser.name }})){% endfor %}
|
||||
@@ -131,6 +131,19 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio
|
||||
- `-r` raw output. Provides a more literal JSON output, typically with string values and no additional semantic processing
|
||||
- `-v` version information
|
||||
|
||||
### Exit Codes
|
||||
Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to determine if an error was from the parsed program or `jc`.
|
||||
|
||||
Consider the following examples using `ifconfig`:
|
||||
|
||||
| `ifconfig` exit code | `jc` exit code | Combined exit code | Interpretation |
|
||||
|----------------------|----------------|--------------------|------------------------------------|
|
||||
| `0` | `0` | `0` | No errors |
|
||||
| `1` | `0` | `1` | Error in `ifconfig` |
|
||||
| `0` | `100` | `100` | Error in `jc` |
|
||||
| `1` | `100` | `101` | Error in both `ifconfig` and `jc` |
|
||||
|
||||
|
||||
### Setting Custom Colors via Environment Variable
|
||||
You can specify custom colors via the `JC_COLORS` environment variable. The `JC_COLORS` environment variable takes four comma separated string values in the following format:
|
||||
```bash
|
||||
@@ -156,12 +169,30 @@ Custom local parser plugins may be placed in a `jc/jcparsers` folder in your loc
|
||||
|
||||
Local parser plugins are standard python module files. Use the [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) parser as a template and simply place a `.py` file in the `jcparsers` subfolder.
|
||||
|
||||
Local plugin filenames must be valid python module names, therefore must consist entirely of alphanumerics and start with a letter. Local plugins may override default plugins.
|
||||
Local plugin filenames must be valid python module names, therefore must consist entirely of alphanumerics and start with a letter. Local plugins may override default parsers.
|
||||
|
||||
> Note: The application data directory follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)
|
||||
|
||||
### Caveats
|
||||
**Locale:**
|
||||
|
||||
For best results set the `LANG` locale environment variable to `C`. For example, either by setting directly on the command-line:
|
||||
```
|
||||
$ LANG=C date | jc --date
|
||||
```
|
||||
or by exporting to the environment before running commands:
|
||||
```
|
||||
$ export LANG=C
|
||||
```
|
||||
|
||||
**Timezones:**
|
||||
|
||||
Some parsers have calculated epoch timestamp fields added to the output. Unless a timestamp field name has a `_utc` suffix it is considered naive. (i.e. based on the local timezone of the system the `jc` parser was run on).
|
||||
|
||||
If a UTC timezone can be detected in the text of the command output, the timestamp will be timezone aware and have a `_utc` suffix on the key name. (e.g. `epoch_utc`) No other timezones are supported for aware timestamps.
|
||||
|
||||
## Compatibility
|
||||
Some parsers like `ls`, `ps`, `dig`, etc. will work on any platform. Other parsers that are platform-specific will generate a warning message if they are used on an unsupported platform. To see all parser information, including compatibility, run `jc -ap`.
|
||||
Some parsers like `dig`, `xml`, `csv`, etc. will work on any platform. Other parsers that convert platform-specific output will generate a warning message if they are run on an unsupported platform. To see all parser information, including compatibility, run `jc -ap`.
|
||||
|
||||
You may still use a parser on an unsupported platform - for example, you may want to parse a file with linux `lsof` output on an macOS laptop. In that case you can suppress the warning message with the `-q` cli option or the `quiet=True` function parameter in `parse()`:
|
||||
|
||||
|
||||
1
tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json
vendored
Normal file
1
tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"destination_ip":"127.0.0.1","data_bytes":56,"pattern":null,"destination":"127.0.0.1","packets_transmitted":20,"packets_received":20,"packet_loss_percent":0.0,"duplicates":0,"time_ms":19070.0,"round_trip_ms_min":0.038,"round_trip_ms_avg":0.047,"round_trip_ms_max":0.08,"round_trip_ms_stddev":0.011,"responses":[{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":1,"ttl":64,"time_ms":0.038,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":2,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":3,"ttl":64,"time_ms":0.044,"duplicate":false},{"type":"unparsable_line","unparsed_line":"64 bytes from 127.0.0.1: error - weird error"},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":5,"ttl":64,"time_ms":0.08,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":6,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":7,"ttl":64,"time_ms":0.047,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":8,"ttl":64,"time_ms":0.04,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":9,"ttl":64,"time_ms":0.052,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":10,"ttl":64,"time_ms":0.044,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":11,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":12,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":13,"ttl":64,"time_ms":0.05,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":14,"ttl":64,"time_ms":0.045,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":15,"ttl":64,"time_ms":0.062,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":16,"ttl":64,"time_ms":0.046,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":17,"ttl":64,"time_ms":0.046,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":18,"ttl":64,"time_ms":0.045,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":19,"ttl":64,"time_ms":0.044,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":20,"ttl":64,"time_ms":0.044,"duplicate":false}]}
|
||||
27
tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out
vendored
Normal file
27
tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
|
||||
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.038 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.043 ms: some weird error
|
||||
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.044 ms
|
||||
64 bytes from 127.0.0.1: error - weird error
|
||||
64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.080 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.043 ms
|
||||
this is a weird error message
|
||||
64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.047 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.040 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.052 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.044 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.043 ms
|
||||
unparsable line
|
||||
64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.043 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.050 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.045 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.062 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.046 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.046 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.045 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.044 ms
|
||||
64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.044 ms
|
||||
|
||||
--- 127.0.0.1 ping statistics ---
|
||||
20 packets transmitted, 20 received, 0% packet loss, time 19070ms
|
||||
rtt min/avg/max/mdev = 0.038/0.047/0.080/0.011 ms
|
||||
1
tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json
vendored
Normal file
1
tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"destination_ip":"2a04:4e42:600::323","data_bytes":56,"pattern":"0xabcd","destination":"2a04:4e42:600::323","packets_transmitted":20,"packets_received":19,"packet_loss_percent":5.0,"duplicates":0,"time_ms":19067.0,"round_trip_ms_min":27.064,"round_trip_ms_avg":33.626,"round_trip_ms_max":38.146,"round_trip_ms_stddev":3.803,"responses":[{"type":"unparsable_line","unparsed_line":"64 bytes from 2a04:4e42:600::323: strange error"},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":2,"ttl":59,"time_ms":28.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":3,"ttl":59,"time_ms":36.0,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":4,"ttl":59,"time_ms":28.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":5,"ttl":59,"time_ms":35.8,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":6,"ttl":59,"time_ms":34.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":7,"ttl":59,"time_ms":30.7,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":8,"ttl":59,"time_ms":28.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":9,"ttl":59,"time_ms":36.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":10,"ttl":59,"time_ms":36.3,"duplicate":false},{"type":"timeout","timestamp":null,"icmp_seq":11},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":12,"ttl":59,"time_ms":37.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":13,"ttl":59,"time_ms":30.7,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":14,"ttl":59,"time_ms":36.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":15,"ttl":59,"time_ms":35.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":16,"ttl":59,"time_ms":36.3,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":17,"ttl":59,"time_ms":37.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":18,"ttl":59,"time_ms":36.2,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":19,"ttl":59,"time_ms":27.0,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":20,"ttl":59,"time_ms":38.1,"duplicate":false}]}
|
||||
27
tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out
vendored
Normal file
27
tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
PATTERN: 0xabcd
|
||||
PING 2a04:4e42:600::323(2a04:4e42:600::323) 56 data bytes
|
||||
64 bytes from 2a04:4e42:600::323: strange error
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=2 ttl=59 time=28.4 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=3 ttl=59 time=36.0 ms
|
||||
strange error here
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=4 ttl=59 time=28.5 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=5 ttl=59 time=35.8 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=6 ttl=59 time=34.4 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=7 ttl=59 time=30.7 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=8 ttl=59 time=28.5 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=9 ttl=59 time=36.5 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=10 ttl=59 time=36.3 ms
|
||||
no answer yet for icmp_seq=11
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=12 ttl=59 time=37.4 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=13 ttl=59 time=30.7 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=14 ttl=59 time=36.5 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=15 ttl=59 time=35.4 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=16 ttl=59 time=36.3 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=17 ttl=59 time=37.5 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=18 ttl=59 time=36.2 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=19 ttl=59 time=27.0 ms
|
||||
64 bytes from 2a04:4e42:600::323: icmp_seq=20 ttl=59 time=38.1 ms
|
||||
|
||||
--- 2a04:4e42:600::323 ping statistics ---
|
||||
20 packets transmitted, 19 received, 5% packet loss, time 19067ms
|
||||
rtt min/avg/max/mdev = 27.064/33.626/38.146/3.803 ms
|
||||
1
tests/fixtures/generic/ufw-appinfo-msn.json
vendored
Normal file
1
tests/fixtures/generic/ufw-appinfo-msn.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"profile":"MSN","title":"MSN Chat","description":"MSN chat protocol (with file transfer and voice)","tcp_list":[1863,6901],"udp_list":[1863,6901],"tcp_ranges":[{"start":6891,"end":6900}],"normalized_tcp_list":[1863,6901],"normalized_tcp_ranges":[{"start":6891,"end":6900}],"normalized_udp_list":[1863,6901]}]
|
||||
9
tests/fixtures/generic/ufw-appinfo-msn.out
vendored
Normal file
9
tests/fixtures/generic/ufw-appinfo-msn.out
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Profile: MSN
|
||||
Title: MSN Chat
|
||||
Description: MSN chat protocol (with file transfer and voice)
|
||||
|
||||
Ports:
|
||||
1863
|
||||
6891:6900/tcp
|
||||
6901
|
||||
|
||||
1
tests/fixtures/generic/ufw-appinfo-test.json
vendored
Normal file
1
tests/fixtures/generic/ufw-appinfo-test.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"profile":"TEST","title":"My test app","description":"a longer description of the test app here.","tcp_list":[1,2,3,4,5,6,7,8,9,10,9,8,7,30,53],"tcp_ranges":[{"start":80,"end":90},{"start":8080,"end":8090}],"udp_ranges":[{"start":50,"end":51},{"start":40,"end":60}],"udp_list":[53],"normalized_tcp_list":[1,2,3,4,5,6,7,8,9,10,30,53],"normalized_tcp_ranges":[{"start":80,"end":90},{"start":8080,"end":8090}],"normalized_udp_ranges":[{"start":40,"end":60}]}]
|
||||
9
tests/fixtures/generic/ufw-appinfo-test.out
vendored
Normal file
9
tests/fixtures/generic/ufw-appinfo-test.out
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Profile: TEST
|
||||
Title: My test app
|
||||
Description: a longer description of the test app here.
|
||||
|
||||
Ports:
|
||||
1,2,3,4,5,6,7,8,9,10,9,8,7,30,80:90,8080:8090/tcp
|
||||
50:51,40:60/udp
|
||||
53
|
||||
|
||||
1
tests/fixtures/generic/ufw-appinfo-test2.json
vendored
Normal file
1
tests/fixtures/generic/ufw-appinfo-test2.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"profile":"TEST2","title":"My test app2","description":"a longer description of the test app here.","tcp_ranges":[{"start":0,"end":65535}],"udp_ranges":[{"start":50,"end":51}],"tcp_list":[53],"udp_list":[53],"normalized_tcp_ranges":[{"start":0,"end":65535}],"normalized_udp_list":[53],"normalized_udp_ranges":[{"start":50,"end":51}]}]
|
||||
9
tests/fixtures/generic/ufw-appinfo-test2.out
vendored
Normal file
9
tests/fixtures/generic/ufw-appinfo-test2.out
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Profile: TEST2
|
||||
Title: My test app2
|
||||
Description: a longer description of the test app here.
|
||||
|
||||
Ports:
|
||||
any/tcp
|
||||
50:51/udp
|
||||
53
|
||||
|
||||
1
tests/fixtures/generic/ufw-appinfo-test3.json
vendored
Normal file
1
tests/fixtures/generic/ufw-appinfo-test3.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"profile":"TEST3","title":"My test app3","description":"test overlapping ports","tcp_list":[80,83,80,53],"tcp_ranges":[{"start":70,"end":90}],"udp_ranges":[{"start":50,"end":51}],"udp_list":[53],"normalized_tcp_list":[53],"normalized_tcp_ranges":[{"start":70,"end":90}],"normalized_udp_list":[53],"normalized_udp_ranges":[{"start":50,"end":51}]}]
|
||||
9
tests/fixtures/generic/ufw-appinfo-test3.out
vendored
Normal file
9
tests/fixtures/generic/ufw-appinfo-test3.out
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Profile: TEST3
|
||||
Title: My test app3
|
||||
Description: test overlapping ports
|
||||
|
||||
Ports:
|
||||
80,83,80,70:90/tcp
|
||||
50:51/udp
|
||||
53
|
||||
|
||||
1
tests/fixtures/generic/ufw-inactive.json
vendored
Normal file
1
tests/fixtures/generic/ufw-inactive.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"status":"inactive","rules":[]}
|
||||
1
tests/fixtures/generic/ufw-inactive.out
vendored
Normal file
1
tests/fixtures/generic/ufw-inactive.out
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Status: inactive
|
||||
1
tests/fixtures/generic/ufw-numbered.json
vendored
Normal file
1
tests/fixtures/generic/ufw-numbered.json
vendored
Normal file
File diff suppressed because one or more lines are too long
23
tests/fixtures/generic/ufw-numbered.out
vendored
Normal file
23
tests/fixtures/generic/ufw-numbered.out
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Status: active
|
||||
Logging: on (low)
|
||||
Default: deny (incoming), allow (outgoing), deny (routed)
|
||||
New profiles: skip
|
||||
|
||||
To Action From
|
||||
-- ------ ----
|
||||
[ 1] 22/tcp ALLOW IN Anywhere
|
||||
[ 2] 22/tcp (v6) ALLOW OUT Anywhere (v6)
|
||||
[ 3] 443/tcp DENY 192.168.0.1
|
||||
[ 4] 443/udp DENY OUT 192.168.0.7 8080:8081
|
||||
[ 5] 22/tcp ALLOW 192.168.0.0/24
|
||||
[ 6] 22/udp ALLOW 192.168.0.0/24 8080:8081 on en0
|
||||
[ 7] 22/tcp (v6) ALLOW IN 2405:204:7449:49fc:f09a:6f4a:bc93:1955/64 on en1
|
||||
[ 8] 80 ALLOW IN Anywhere
|
||||
[ 9] 8080 (v6) ALLOW IN Anywhere (v6)
|
||||
[10] Apache Full ALLOW IN Anywhere
|
||||
[11] Apache Full (v6) ALLOW IN Anywhere (v6)
|
||||
[12] OpenSSH (v6) DENY IN Anywhere (v6)
|
||||
[13] 10.10.10.10 8080 on enp34s0 ALLOW 127.0.0.1 8000
|
||||
[14] 50200:50300/tcp (v6) ALLOW Anywhere (v6)
|
||||
[15] Anywhere (v6) ALLOW IN 2405:204:7449:49fc:f09a:6f4a:bc93:1955
|
||||
|
||||
1
tests/fixtures/generic/ufw-numbered2.json
vendored
Normal file
1
tests/fixtures/generic/ufw-numbered2.json
vendored
Normal file
File diff suppressed because one or more lines are too long
18
tests/fixtures/generic/ufw-numbered2.out
vendored
Normal file
18
tests/fixtures/generic/ufw-numbered2.out
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
Status: active
|
||||
To Action From
|
||||
-- ------ ----
|
||||
[ 1] 224.0.0.251 mDNS ALLOW IN Anywhere
|
||||
[ 2] Anywhere ALLOW IN 123.123.123.123
|
||||
[ 3] 25 ALLOW IN Anywhere
|
||||
[ 4] 80 ALLOW IN Anywhere
|
||||
[ 5] 443 ALLOW IN Anywhere
|
||||
[ 6] 465 ALLOW IN Anywhere
|
||||
[ 7] 993 ALLOW IN Anywhere
|
||||
[ 8] 995 ALLOW IN Anywhere
|
||||
[ 9] ff02::fb mDNS ALLOW IN Anywhere (v6)
|
||||
[10] 25 (v6) ALLOW IN Anywhere (v6)
|
||||
[11] 80 (v6) ALLOW IN Anywhere (v6)
|
||||
[12] 443 (v6) ALLOW IN Anywhere (v6)
|
||||
[13] 465 (v6) ALLOW IN Anywhere (v6)
|
||||
[14] 993 (v6) ALLOW IN Anywhere (v6)
|
||||
[15] 995 (v6) ALLOW IN Anywhere (v6)
|
||||
1
tests/fixtures/generic/ufw.json
vendored
Normal file
1
tests/fixtures/generic/ufw.json
vendored
Normal file
File diff suppressed because one or more lines are too long
22
tests/fixtures/generic/ufw.out
vendored
Normal file
22
tests/fixtures/generic/ufw.out
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Status: active
|
||||
Logging: on (low)
|
||||
Default: deny (incoming), allow (outgoing), deny (routed)
|
||||
New profiles: skip
|
||||
|
||||
To Action From
|
||||
-- ------ ----
|
||||
22/tcp ALLOW IN Anywhere
|
||||
22/tcp (v6) ALLOW OUT Anywhere (v6)
|
||||
443/tcp DENY 192.168.0.1 # nice comment
|
||||
443/udp DENY OUT 192.168.0.7 8080:8081
|
||||
22/tcp ALLOW 192.168.0.0/24
|
||||
22/udp ALLOW 192.168.0.0/24 8080:8081 on en0
|
||||
22/tcp (v6) ALLOW FWD 2405:204:7449:49fc:f09a:6f4a:bc93:1955/64 on en1 #commenting this rule
|
||||
80 ALLOW IN Anywhere
|
||||
8080 (v6) REJECT IN Anywhere (v6)
|
||||
Apache Full ALLOW IN Anywhere # a comment
|
||||
Apache Full (v6) ALLOW IN Anywhere (v6)
|
||||
OpenSSH (v6) DENY IN Anywhere (v6)
|
||||
10.10.10.10 8080 on enp34s0 ALLOW 127.0.0.1 8000
|
||||
50200:50300/tcp (v6) DENY FWD Anywhere (v6)
|
||||
Anywhere (v6) LIMIT 2405:204:7449:49fc:f09a:6f4a:bc93:1955 # this is a comment
|
||||
1
tests/fixtures/osx-10.14.6/dig-noall-answer.json
vendored
Normal file
1
tests/fixtures/osx-10.14.6/dig-noall-answer.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"answer":[{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.65.67"},{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.193.67"},{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.129.67"},{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.1.67"}]}]
|
||||
4
tests/fixtures/osx-10.14.6/dig-noall-answer.out
vendored
Normal file
4
tests/fixtures/osx-10.14.6/dig-noall-answer.out
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
cnn.com. 47 IN A 151.101.65.67
|
||||
cnn.com. 47 IN A 151.101.193.67
|
||||
cnn.com. 47 IN A 151.101.129.67
|
||||
cnn.com. 47 IN A 151.101.1.67
|
||||
1
tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json
vendored
Normal file
1
tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"destination_ip":"192.168.1.220","data_bytes":56,"pattern":null,"destination":"192.168.1.220","packets_transmitted":8,"packets_received":0,"packet_loss_percent":100.0,"duplicates":0,"responses":[{"type":"timeout","icmp_seq":0,"duplicate":false},{"type":"timeout","icmp_seq":1,"duplicate":false},{"type":"unparsable_line","unparsed_line":"92 bytes from fgt1.attlocal.net (192.168.1.220) Destination Network Unreachable"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":11887,"flg":0,"off":0,"ttl":63,"pro":1,"cks":51248,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"unparsable_line","unparsed_line":"92 bytes from fgt1.attlocal.net (192.168.1.220) Weird error message"},{"type":"timeout","icmp_seq":2,"duplicate":false},{"type":"timeout","icmp_seq":3,"duplicate":false},{"type":"timeout","icmp_seq":4,"duplicate":false},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":40674,"flg":0,"off":0,"ttl":63,"pro":1,"cks":22461,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":31035,"flg":0,"off":0,"ttl":63,"pro":1,"cks":32100,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":53536,"flg":0,"off":0,"ttl":63,"pro":1,"cks":9599,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"timeout","icmp_seq":5,"duplicate":false},{"type":"timeout","icmp_seq":6,"duplicate":false}]}
|
||||
35
tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out
vendored
Normal file
35
tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
PING 192.168.1.220 (192.168.1.220): 56 data bytes
|
||||
Request timeout for icmp_seq 0
|
||||
Request timeout for icmp_seq 1
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Network Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 567b 0 0000 3f 01 a024 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 2e6f 0 0000 3f 01 c830 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Weird error message
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 a1ed 0 0000 3f 01 54b2 192.168.1.221 192.168.1.220
|
||||
|
||||
Request timeout for icmp_seq 2
|
||||
Request timeout for icmp_seq 3
|
||||
Request timeout for icmp_seq 4
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 9ee2 0 0000 3f 01 57bd 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 793b 0 0000 3f 01 7d64 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 d120 0 0000 3f 01 257f 192.168.1.221 192.168.1.220
|
||||
|
||||
Request timeout for icmp_seq 5
|
||||
Request timeout for icmp_seq 6
|
||||
|
||||
--- 192.168.1.220 ping statistics ---
|
||||
8 packets transmitted, 0 packets received, 100.0% packet loss
|
||||
1
tests/fixtures/osx-10.14.6/ping-ip-unreachable.json
vendored
Normal file
1
tests/fixtures/osx-10.14.6/ping-ip-unreachable.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"destination_ip":"192.168.1.220","data_bytes":56,"pattern":null,"destination":"192.168.1.220","packets_transmitted":8,"packets_received":0,"packet_loss_percent":100.0,"duplicates":0,"responses":[{"type":"timeout","icmp_seq":0,"duplicate":false},{"type":"timeout","icmp_seq":1,"duplicate":false},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":22139,"flg":0,"off":0,"ttl":63,"pro":1,"cks":40996,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":11887,"flg":0,"off":0,"ttl":63,"pro":1,"cks":51248,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":41453,"flg":0,"off":0,"ttl":63,"pro":1,"cks":21682,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"timeout","icmp_seq":2,"duplicate":false},{"type":"timeout","icmp_seq":3,"duplicate":false},{"type":"timeout","icmp_seq":4,"duplicate":false},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":40674,"flg":0,"off":0,"ttl":63,"pro":1,"cks":22461,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":31035,"flg":0,"off":0,"ttl":63,"pro":1,"cks":32100,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":53536,"flg":0,"off":0,"ttl":63,"pro":1,"cks":9599,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"timeout","icmp_seq":5,"duplicate":false},{"type":"timeout","icmp_seq":6,"duplicate":false}]}
|
||||
35
tests/fixtures/osx-10.14.6/ping-ip-unreachable.out
vendored
Normal file
35
tests/fixtures/osx-10.14.6/ping-ip-unreachable.out
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
PING 192.168.1.220 (192.168.1.220): 56 data bytes
|
||||
Request timeout for icmp_seq 0
|
||||
Request timeout for icmp_seq 1
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 567b 0 0000 3f 01 a024 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 2e6f 0 0000 3f 01 c830 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 a1ed 0 0000 3f 01 54b2 192.168.1.221 192.168.1.220
|
||||
|
||||
Request timeout for icmp_seq 2
|
||||
Request timeout for icmp_seq 3
|
||||
Request timeout for icmp_seq 4
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 9ee2 0 0000 3f 01 57bd 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 793b 0 0000 3f 01 7d64 192.168.1.221 192.168.1.220
|
||||
|
||||
92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable
|
||||
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
|
||||
4 5 00 5400 d120 0 0000 3f 01 257f 192.168.1.221 192.168.1.220
|
||||
|
||||
Request timeout for icmp_seq 5
|
||||
Request timeout for icmp_seq 6
|
||||
|
||||
--- 192.168.1.220 ping statistics ---
|
||||
8 packets transmitted, 0 packets received, 100.0% packet loss
|
||||
1
tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json
vendored
Normal file
1
tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"source_ip":"::1","destination_ip":"::1","data_bytes":56,"pattern":null,"destination":"::1","packets_transmitted":3,"packets_received":3,"packet_loss_percent":0.0,"duplicates":0,"round_trip_ms_min":0.071,"round_trip_ms_avg":0.115,"round_trip_ms_max":0.153,"round_trip_ms_stddev":0.034,"responses":[{"type":"reply","bytes":16,"response_ip":"::1","icmp_seq":0,"ttl":64,"time_ms":0.071,"duplicate":false},{"type":"unparsable_line","unparsed_line":"16 bytes from ::1 strange error"},{"type":"reply","bytes":16,"response_ip":"::1","icmp_seq":2,"ttl":64,"time_ms":0.122,"duplicate":false}]}
|
||||
9
tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out
vendored
Normal file
9
tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
PING6(56=40+8+8 bytes) ::1 --> ::1
|
||||
16 bytes from ::1, icmp_seq=0 hlim=64 time=0.071 ms
|
||||
16 bytes from ::1, strange error
|
||||
weird error message
|
||||
16 bytes from ::1, icmp_seq=2 hlim=64 time=0.122 ms
|
||||
|
||||
--- ::1 ping6 statistics ---
|
||||
3 packets transmitted, 3 packets received, 0.0% packet loss
|
||||
round-trip min/avg/max/std-dev = 0.071/0.115/0.153/0.034 ms
|
||||
1
tests/fixtures/ubuntu-18.04/ufw-appinfo-all.json
vendored
Normal file
1
tests/fixtures/ubuntu-18.04/ufw-appinfo-all.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"profile":"MSN","title":"MSN Chat","description":"MSN chat protocol (with file transfer and voice)","tcp_list":[1863,6901],"udp_list":[1863,6901],"tcp_ranges":[{"start":6891,"end":6900}],"normalized_tcp_list":[1863,6901],"normalized_tcp_ranges":[{"start":6891,"end":6900}],"normalized_udp_list":[1863,6901]},{"profile":"OpenSSH","title":"Secure shell server, an rshd replacement","description":"OpenSSH is a free implementation of the Secure Shell protocol.","tcp_list":[22],"normalized_tcp_list":[22]},{"profile":"TEST","title":"My test app","description":"a longer description of the test app here.","tcp_list":[1,2,3,4,5,6,7,8,9,10,30,53],"tcp_ranges":[{"start":80,"end":90},{"start":8080,"end":8090}],"udp_ranges":[{"start":50,"end":51}],"udp_list":[53],"normalized_tcp_list":[1,2,3,4,5,6,7,8,9,10,30,53],"normalized_tcp_ranges":[{"start":80,"end":90},{"start":8080,"end":8090}],"normalized_udp_list":[53],"normalized_udp_ranges":[{"start":50,"end":51}]},{"profile":"TEST2","title":"My test app2","description":"a longer description of the test app here.","tcp_ranges":[{"start":0,"end":65535}],"udp_ranges":[{"start":50,"end":51}],"tcp_list":[53],"udp_list":[53],"normalized_tcp_ranges":[{"start":0,"end":65535}],"normalized_udp_list":[53],"normalized_udp_ranges":[{"start":50,"end":51}]},{"profile":"TEST3","title":"My test app3","description":"test overlapping ports","tcp_list":[80,83,80,53],"tcp_ranges":[{"start":70,"end":90}],"udp_ranges":[{"start":50,"end":51}],"udp_list":[53],"normalized_tcp_list":[53],"normalized_tcp_ranges":[{"start":70,"end":90}],"normalized_udp_list":[53],"normalized_udp_ranges":[{"start":50,"end":51}]}]
|
||||
51
tests/fixtures/ubuntu-18.04/ufw-appinfo-all.out
vendored
Normal file
51
tests/fixtures/ubuntu-18.04/ufw-appinfo-all.out
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
Profile: MSN
|
||||
Title: MSN Chat
|
||||
Description: MSN chat protocol (with file transfer and voice)
|
||||
|
||||
Ports:
|
||||
1863
|
||||
6891:6900/tcp
|
||||
6901
|
||||
|
||||
--
|
||||
|
||||
Profile: OpenSSH
|
||||
Title: Secure shell server, an rshd replacement
|
||||
Description: OpenSSH is a free implementation of the Secure Shell protocol.
|
||||
|
||||
Port:
|
||||
22/tcp
|
||||
|
||||
--
|
||||
|
||||
Profile: TEST
|
||||
Title: My test app
|
||||
Description: a longer description of the test app here.
|
||||
|
||||
Ports:
|
||||
1,2,3,4,5,6,7,8,9,10,30,80:90,8080:8090/tcp
|
||||
50:51/udp
|
||||
53
|
||||
|
||||
--
|
||||
|
||||
Profile: TEST2
|
||||
Title: My test app2
|
||||
Description: a longer description of the test app here.
|
||||
|
||||
Ports:
|
||||
any/tcp
|
||||
50:51/udp
|
||||
53
|
||||
|
||||
--
|
||||
|
||||
Profile: TEST3
|
||||
Title: My test app3
|
||||
Description: test overlapping ports
|
||||
|
||||
Ports:
|
||||
80,83,80,70:90/tcp
|
||||
50:51/udp
|
||||
53
|
||||
|
||||
1
tests/fixtures/ubuntu-18.04/ufw-numbered.json
vendored
Normal file
1
tests/fixtures/ubuntu-18.04/ufw-numbered.json
vendored
Normal file
File diff suppressed because one or more lines are too long
19
tests/fixtures/ubuntu-18.04/ufw-numbered.out
vendored
Normal file
19
tests/fixtures/ubuntu-18.04/ufw-numbered.out
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Status: active
|
||||
|
||||
To Action From
|
||||
-- ------ ----
|
||||
[ 1] 22 ALLOW IN Anywhere
|
||||
[ 2] 80,443/tcp ALLOW IN Anywhere
|
||||
[ 3] 80 on eth0 ALLOW IN Anywhere # test
|
||||
[ 4] 10.0.0.1/ipv6 ALLOW IN 10.4.0.0/16/ipv6
|
||||
[ 5] 10.0.0.1/esp ALLOW IN Anywhere
|
||||
[ 6] 10.0.0.1/esp ALLOW IN 10.4.0.0/16/esp
|
||||
[ 7] 10.0.0.1/ah ALLOW IN Anywhere
|
||||
[ 8] 10.0.0.1/ah ALLOW IN 10.4.0.0/16/ah
|
||||
[ 9] 100:200,300:400/tcp ALLOW IN Anywhere
|
||||
[10] 1,2,100:200,300:400/udp ALLOW IN Anywhere
|
||||
[11] 22 (v6) ALLOW IN Anywhere (v6)
|
||||
[12] 80,443/tcp (v6) ALLOW IN Anywhere (v6)
|
||||
[13] 80 (v6) on eth0 ALLOW IN Anywhere (v6) # test
|
||||
[14] 100:200,300:400/tcp (v6) ALLOW IN Anywhere (v6)
|
||||
[15] 1,2,100:200,300:400/udp (v6) ALLOW IN Anywhere (v6)
|
||||
1
tests/fixtures/ubuntu-18.04/ufw-verbose.json
vendored
Normal file
1
tests/fixtures/ubuntu-18.04/ufw-verbose.json
vendored
Normal file
File diff suppressed because one or more lines are too long
22
tests/fixtures/ubuntu-18.04/ufw-verbose.out
vendored
Normal file
22
tests/fixtures/ubuntu-18.04/ufw-verbose.out
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Status: active
|
||||
Logging: on (low)
|
||||
Default: deny (incoming), allow (outgoing), disabled (routed)
|
||||
New profiles: skip
|
||||
|
||||
To Action From
|
||||
-- ------ ----
|
||||
22 ALLOW IN Anywhere
|
||||
80,443/tcp ALLOW IN Anywhere
|
||||
80 on eth0 ALLOW IN Anywhere # test
|
||||
10.0.0.1/ipv6 ALLOW IN 10.4.0.0/16/ipv6
|
||||
10.0.0.1/esp ALLOW IN Anywhere
|
||||
10.0.0.1/esp ALLOW IN 10.4.0.0/16/esp
|
||||
10.0.0.1/ah ALLOW IN Anywhere
|
||||
10.0.0.1/ah ALLOW IN 10.4.0.0/16/ah
|
||||
100:200,300:400/tcp ALLOW IN Anywhere
|
||||
1,2,100:200,300:400/udp ALLOW IN Anywhere
|
||||
22 (v6) ALLOW IN Anywhere (v6)
|
||||
80,443/tcp (v6) ALLOW IN Anywhere (v6)
|
||||
80 (v6) on eth0 ALLOW IN Anywhere (v6) # test
|
||||
100:200,300:400/tcp (v6) ALLOW IN Anywhere (v6)
|
||||
1,2,100:200,300:400/udp (v6) ALLOW IN Anywhere (v6)
|
||||
@@ -5,31 +5,31 @@ import jc.cli
|
||||
|
||||
|
||||
class MyTests(unittest.TestCase):
|
||||
def test_cli_generate_magic_command(self):
|
||||
def test_cli_magic_parser(self):
|
||||
commands = {
|
||||
'jc -p systemctl list-sockets': 'systemctl list-sockets | jc --systemctl-ls -p',
|
||||
'jc -p systemctl list-unit-files': 'systemctl list-unit-files | jc --systemctl-luf -p',
|
||||
'jc -p pip list': 'pip list | jc --pip-list -p',
|
||||
'jc -p pip3 list': 'pip3 list | jc --pip-list -p',
|
||||
'jc -p pip show jc': 'pip show jc | jc --pip-show -p',
|
||||
'jc -p pip3 show jc': 'pip3 show jc | jc --pip-show -p',
|
||||
'jc -prd last': 'last | jc --last -prd',
|
||||
'jc -prd lastb': 'lastb | jc --last -prd',
|
||||
'jc -p airport -I': 'airport -I | jc --airport -p',
|
||||
'jc -p -r airport -I': 'airport -I | jc --airport -pr',
|
||||
'jc -prd airport -I': 'airport -I | jc --airport -prd',
|
||||
'jc -p nonexistent command': 'nonexistent command',
|
||||
'jc -ap': None,
|
||||
'jc -a arp -a': None,
|
||||
'jc -v': None,
|
||||
'jc -h': None,
|
||||
'jc -h --arp': None,
|
||||
'jc -h arp': None,
|
||||
'jc -h arp -a': None
|
||||
'jc -p systemctl list-sockets': (True, ['systemctl', 'list-sockets'], '--systemctl-ls', ['p']),
|
||||
'jc -p systemctl list-unit-files': (True, ['systemctl', 'list-unit-files'], '--systemctl-luf', ['p']),
|
||||
'jc -p pip list': (True, ['pip', 'list'], '--pip-list', ['p']),
|
||||
'jc -p pip3 list': (True, ['pip3', 'list'], '--pip-list', ['p']),
|
||||
'jc -p pip show jc': (True, ['pip', 'show', 'jc'], '--pip-show', ['p']),
|
||||
'jc -p pip3 show jc': (True, ['pip3', 'show', 'jc'], '--pip-show', ['p']),
|
||||
'jc -prd last': (True, ['last'], '--last', ['p', 'r', 'd']),
|
||||
'jc -prdd lastb': (True, ['lastb'], '--last', ['p', 'r', 'd', 'd']),
|
||||
'jc -p airport -I': (True, ['airport', '-I'], '--airport', ['p']),
|
||||
'jc -p -r airport -I': (True, ['airport', '-I'], '--airport', ['p', 'r']),
|
||||
'jc -prd airport -I': (True, ['airport', '-I'], '--airport', ['p', 'r', 'd']),
|
||||
'jc -p nonexistent command': (False, ['nonexistent', 'command'], None, ['p']),
|
||||
'jc -ap': (False, None, None, []),
|
||||
'jc -a arp -a': (False, None, None, []),
|
||||
'jc -v': (False, None, None, []),
|
||||
'jc -h': (False, None, None, []),
|
||||
'jc -h --arp': (False, None, None, []),
|
||||
'jc -h arp': (False, None, None, []),
|
||||
'jc -h arp -a': (False, None, None, [])
|
||||
}
|
||||
|
||||
for command, expected_command in commands.items():
|
||||
self.assertEqual(jc.cli.generate_magic_command(command.split(' '))[1], expected_command)
|
||||
self.assertEqual(jc.cli.magic_parser(command.split(' ')), expected_command)
|
||||
|
||||
def test_cli_set_env_colors(self):
|
||||
if pygments.__version__.startswith('2.3.'):
|
||||
|
||||
@@ -55,10 +55,12 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-axfr.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_dig_axfr = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-noall-answer.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_dig_noall_answer = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-answer-spaces.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_dig_answer_spaces = f.read()
|
||||
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-additional.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_dig_additional = f.read()
|
||||
|
||||
@@ -123,10 +125,12 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-axfr.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_dig_axfr_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-noall-answer.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_dig_noall_answer_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-answer-spaces.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_dig_answer_spaces_json = json.loads(f.read())
|
||||
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-additional.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_dig_additional_json = json.loads(f.read())
|
||||
|
||||
@@ -241,6 +245,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.dig.parse(self.osx_10_14_6_dig_axfr, quiet=True), self.osx_10_14_6_dig_axfr_json)
|
||||
|
||||
def test_dig_noall_answer_osx_10_14_6(self):
|
||||
"""
|
||||
Test 'dig +noall +answer' on OSX 10.14.6
|
||||
"""
|
||||
self.assertEqual(jc.parsers.dig.parse(self.osx_10_14_6_dig_noall_answer, quiet=True), self.osx_10_14_6_dig_noall_answer_json)
|
||||
|
||||
def test_dig_answer_spaces(self):
|
||||
"""
|
||||
Test 'dig' with spaces in the answer data (e.g. TXT responses)
|
||||
|
||||
@@ -30,6 +30,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p.out'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_O_p = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_O_p_unparsable = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-D-p.out'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_O_D_p = f.read()
|
||||
|
||||
@@ -45,6 +48,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-dup.out'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_dup = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping_ip_O_unparsedlines = f.read()
|
||||
|
||||
# ubuntu
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ping-ip-O.out'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_4_ping_ip_O = f.read()
|
||||
@@ -157,6 +163,12 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unreachable.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip_unreachable = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip_unknown_errors = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-hostname-p.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping6_hostname_p = f.read()
|
||||
|
||||
@@ -175,6 +187,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping6_ip = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping6_ip_unparsable = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-dup.out'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip_dup = f.read()
|
||||
|
||||
@@ -209,6 +224,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p.json'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_O_p_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_O_p_unparsable_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-D-p.json'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_O_D_p_json = json.loads(f.read())
|
||||
|
||||
@@ -224,6 +242,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-dup.json'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping6_ip_dup_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json'), 'r', encoding='utf-8') as f:
|
||||
self.centos_7_7_ping_ip_O_unparsedlines_json = json.loads(f.read())
|
||||
|
||||
# ubunutu
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ping-ip-O.json'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_4_ping_ip_O_json = json.loads(f.read())
|
||||
@@ -336,6 +357,12 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unreachable.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip_unreachable_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip_unknown_errors_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-hostname-p.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping6_hostname_p_json = json.loads(f.read())
|
||||
|
||||
@@ -354,6 +381,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping6_ip_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping6_ip_unparsable_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-dup.json'), 'r', encoding='utf-8') as f:
|
||||
self.osx_10_14_6_ping_ip_dup_json = json.loads(f.read())
|
||||
|
||||
@@ -409,6 +439,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_O_p, quiet=True), self.centos_7_7_ping6_ip_O_p_json)
|
||||
|
||||
def test_ping6_ip_O_p_unparsable_centos_7_7(self):
|
||||
"""
|
||||
Test 'ping6 <ip> -O -p' with unparsable lines on Centos 7.7
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_O_p_unparsable, quiet=True), self.centos_7_7_ping6_ip_O_p_unparsable_json)
|
||||
|
||||
def test_ping6_ip_O_D_p_centos_7_7(self):
|
||||
"""
|
||||
Test 'ping6 <ip> -O -D -p' on Centos 7.7
|
||||
@@ -439,6 +475,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_dup, quiet=True), self.centos_7_7_ping6_ip_dup_json)
|
||||
|
||||
def test_ping_ip_O_unparsedlines_centos_7_7(self):
|
||||
"""
|
||||
Test 'ping <ip> -O' on Centos 7.7 with unparsable lines and error messages
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping_ip_O_unparsedlines, quiet=True), self.centos_7_7_ping_ip_O_unparsedlines_json)
|
||||
|
||||
def test_ping_ip_O_ubuntu_18_4(self):
|
||||
"""
|
||||
Test 'ping <ip> -O' on Ubuntu 18.4
|
||||
@@ -651,10 +693,22 @@ class MyTests(unittest.TestCase):
|
||||
|
||||
def test_ping_ip_osx_10_14_6(self):
|
||||
"""
|
||||
Test 'ping6 <ip>' on osx 10.14.6
|
||||
Test 'ping <ip>' on osx 10.14.6
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping_ip, quiet=True), self.osx_10_14_6_ping_ip_json)
|
||||
|
||||
def test_ping_ip_unreachable_osx_10_14_6(self):
|
||||
"""
|
||||
Test 'ping <ip>' with host unreachable error on osx 10.14.6
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping_ip_unreachable, quiet=True), self.osx_10_14_6_ping_ip_unreachable_json)
|
||||
|
||||
def test_ping_ip_unknown_errors_osx_10_14_6(self):
|
||||
"""
|
||||
Test 'ping <ip>' with unknown/unparsable errors on osx 10.14.6
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping_ip_unknown_errors, quiet=True), self.osx_10_14_6_ping_ip_unknown_errors_json)
|
||||
|
||||
def test_ping6_hostname_p_osx_10_14_6(self):
|
||||
"""
|
||||
Test 'ping6 <hostname> -p' on osx 10.14.6
|
||||
@@ -691,6 +745,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping6_ip, quiet=True), self.osx_10_14_6_ping6_ip_json)
|
||||
|
||||
def test_ping6_ip_unparsable_osx_10_14_6(self):
|
||||
"""
|
||||
Test 'ping6 <ip>' with unparsable lines on osx 10.14.6
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping6_ip_unparsable, quiet=True), self.osx_10_14_6_ping6_ip_unparsable_json)
|
||||
|
||||
def test_ping_ip_dup_osx_10_14_6(self):
|
||||
"""
|
||||
Test 'ping <ip>' to broadcast IP to get duplicate replies on osx 10.14.6
|
||||
|
||||
94
tests/test_ufw.py
Normal file
94
tests/test_ufw.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import os
|
||||
import json
|
||||
import unittest
|
||||
import jc.parsers.ufw
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class MyTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# input
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ufw-verbose.out'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_04_ufw_verbose = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ufw-numbered.out'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_04_ufw_numbered = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-numbered.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_numbered = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-numbered2.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_numbered2 = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-inactive.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_inactive = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ufw-verbose.json'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_04_ufw_verbose_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ufw-numbered.json'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_04_ufw_numbered_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-numbered.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_numbered_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-numbered2.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_numbered2_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-inactive.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_inactive_json = json.loads(f.read())
|
||||
|
||||
def test_ufw_nodata(self):
|
||||
"""
|
||||
Test 'ufw' with no data
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw.parse('', quiet=True), {})
|
||||
|
||||
def test_ufw_ubuntu_18_04_verbose(self):
|
||||
"""
|
||||
Test 'ufw status verbose' on Ubuntu 18.04
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw.parse(self.ubuntu_18_04_ufw_verbose, quiet=True), self.ubuntu_18_04_ufw_verbose_json)
|
||||
|
||||
def test_ufw_ubuntu_18_04_numbered(self):
|
||||
"""
|
||||
Test 'ufw status numbered' on Ubuntu 18.04
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw.parse(self.ubuntu_18_04_ufw_numbered, quiet=True), self.ubuntu_18_04_ufw_numbered_json)
|
||||
|
||||
def test_ufw_generic_verbose(self):
|
||||
"""
|
||||
Test 'ufw status verbose' sample
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw.parse(self.generic_ufw, quiet=True), self.generic_ufw_json)
|
||||
|
||||
def test_ufw_generic_verbose_numbered(self):
|
||||
"""
|
||||
Test 'ufw status verbose numbered' sample
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw.parse(self.generic_ufw_numbered, quiet=True), self.generic_ufw_numbered_json)
|
||||
|
||||
def test_ufw_generic_verbose_numbered2(self):
|
||||
"""
|
||||
Test 'ufw status verbose numbered' sample
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw.parse(self.generic_ufw_numbered2, quiet=True), self.generic_ufw_numbered2_json)
|
||||
|
||||
def test_ufw_generic_inactive(self):
|
||||
"""
|
||||
Test 'ufw status' when firewall is inactive
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw.parse(self.generic_ufw_inactive, quiet=True), self.generic_ufw_inactive_json)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
82
tests/test_ufw_appinfo.py
Normal file
82
tests/test_ufw_appinfo.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import os
|
||||
import json
|
||||
import unittest
|
||||
import jc.parsers.ufw_appinfo
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class MyTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# input
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ufw-appinfo-all.out'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_04_ufw_appinfo_all = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-test.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_test = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-test2.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_test2 = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-test3.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_test3 = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-msn.out'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_msn = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ufw-appinfo-all.json'), 'r', encoding='utf-8') as f:
|
||||
self.ubuntu_18_04_ufw_appinfo_all_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-test.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_test_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-test2.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_test2_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-test3.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_test3_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ufw-appinfo-msn.json'), 'r', encoding='utf-8') as f:
|
||||
self.generic_ufw_appinfo_msn_json = json.loads(f.read())
|
||||
|
||||
def test_ufw_appinfo_nodata(self):
|
||||
"""
|
||||
Test 'ufw_appinfo' with no data
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw_appinfo.parse('', quiet=True), [])
|
||||
|
||||
def test_ufw_appinfo_ubuntu_18_04_all(self):
|
||||
"""
|
||||
Test 'ufw app info all' on Ubuntu 18.04
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw_appinfo.parse(self.ubuntu_18_04_ufw_appinfo_all, quiet=True), self.ubuntu_18_04_ufw_appinfo_all_json)
|
||||
|
||||
def test_ufw_appinfo_generic_test(self):
|
||||
"""
|
||||
Test 'ufw app info [application]' sample
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw_appinfo.parse(self.generic_ufw_appinfo_test, quiet=True), self.generic_ufw_appinfo_test_json)
|
||||
|
||||
def test_ufw_appinfo_generic_test2(self):
|
||||
"""
|
||||
Test 'ufw app info [application]' sample
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw_appinfo.parse(self.generic_ufw_appinfo_test2, quiet=True), self.generic_ufw_appinfo_test2_json)
|
||||
|
||||
def test_ufw_appinfo_generic_test3(self):
|
||||
"""
|
||||
Test 'ufw app info [application]' sample
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw_appinfo.parse(self.generic_ufw_appinfo_test3, quiet=True), self.generic_ufw_appinfo_test3_json)
|
||||
|
||||
def test_ufw_appinfo_generic_msn(self):
|
||||
"""
|
||||
Test 'ufw app info MSN' sample
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ufw_appinfo.parse(self.generic_ufw_appinfo_msn, quiet=True), self.generic_ufw_appinfo_msn_json)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -17,6 +17,8 @@ class MyTests(unittest.TestCase):
|
||||
# Windows english format wint non-UTC tz (found in systeminfo cli output)
|
||||
'3/22/2021, 1:15:51 PM (UTC-0600)': {'string': '3/22/2021, 1:15:51 PM (UTC-0600)', 'format': 1700, 'naive': 1616444151, 'utc': None},
|
||||
# Windows english format with UTC tz (found in systeminfo cli output)
|
||||
'3/22/2021, 1:15:51 PM (UTC)': {'string': '3/22/2021, 1:15:51 PM (UTC)', 'format': 1705, 'naive': 1616444151, 'utc': 1616418951},
|
||||
# Windows english format with UTC tz (found in systeminfo cli output)
|
||||
'3/22/2021, 1:15:51 PM (UTC+0000)': {'string': '3/22/2021, 1:15:51 PM (UTC+0000)', 'format': 1710, 'naive': 1616444151, 'utc': 1616418951},
|
||||
# en_US.UTF-8 local format (found in upower cli output)
|
||||
'Tue 23 Mar 2021 04:12:11 PM UTC': {'string': 'Tue 23 Mar 2021 04:12:11 PM UTC', 'format': 2000, 'naive': 1616541131, 'utc': 1616515931},
|
||||
@@ -45,7 +47,7 @@ class MyTests(unittest.TestCase):
|
||||
for input_string, expected_output in datetime_map.items():
|
||||
self.assertEqual(jc.utils.timestamp(input_string).__dict__, expected_output)
|
||||
|
||||
def test_convert_to_int(self):
|
||||
def test_utils_convert_to_int(self):
|
||||
io_map = {
|
||||
None: None,
|
||||
True: 1,
|
||||
@@ -72,7 +74,7 @@ class MyTests(unittest.TestCase):
|
||||
for input_string, expected_output in io_map.items():
|
||||
self.assertEqual(jc.utils.convert_to_int(input_string), expected_output)
|
||||
|
||||
def test_convert_to_float(self):
|
||||
def test_utils_convert_to_float(self):
|
||||
io_map = {
|
||||
None: None,
|
||||
True: 1.0,
|
||||
@@ -99,7 +101,7 @@ class MyTests(unittest.TestCase):
|
||||
for input_string, expected_output in io_map.items():
|
||||
self.assertEqual(jc.utils.convert_to_float(input_string), expected_output)
|
||||
|
||||
def test_convert_to_bool(self):
|
||||
def test_utils_convert_to_bool(self):
|
||||
io_map = {
|
||||
None: False,
|
||||
True: True,
|
||||
|
||||
Reference in New Issue
Block a user