net bug fix, myextapi now service agnostic
This commit is contained in:
@@ -124,7 +124,7 @@ A bar-style prompt showing current time, execution time of the last command
|
||||
| `mcd` | filefct | Create a directory and immediately move into it |
|
||||
| `mdcat` | disp | Render Markdown files (or stdin) in terminal with colors, inline formatting, tables and framed code blocks; honours `NO_COLOR` |
|
||||
| `meteo` | info | Display weather forecast for the configured or given city |
|
||||
| `myextip` | net | Get information about your public IP address |
|
||||
| `myextip` | net | Get information about your public IP address (IP, ISP, location, coordinates, AS, timezone) from an HTTPS lookup service; needs `jq` for the formatted output, `-R` shows the raw JSON |
|
||||
| `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) |
|
||||
| `pinfo` | info | Display system information read from `/proc` and `/sys` only |
|
||||
| `pkgf` | packages | Find which installed package owns a given file (distro-aware via `get_pkgmgr`) |
|
||||
@@ -307,7 +307,7 @@ change the default without having to pass flags every time.
|
||||
| --- | --- | --- |
|
||||
| `DWL_PREFERRED_TOOL` | _(empty)_ | Force `dwl` to use `curl`, `wget`, or `fetch` (auto-detected when unset) |
|
||||
| `DWL_DEFAULT_RESUME` | `0` | Enable `dwl` resume mode by default (`1`/`true`/`yes`/`on`); applies to file downloads |
|
||||
| `MYEXTIP_DEFAULT_URL` | `https://ip-api.com/json` | API endpoint used by `myextip` |
|
||||
| `MYEXTIP_DEFAULT_URL` | `https://ipinfo.io/json` | HTTPS JSON API used by `myextip`. `https://ipwho.is/` is also supported, as is the (HTTP-only on its free tier) ip-api.com format; a non-HTTPS URL triggers a warning |
|
||||
|
||||
**`[packages]`**
|
||||
|
||||
|
||||
@@ -96,6 +96,16 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
||||
`setlocale fr_FR` could pick a non-UTF-8 charset; shortcut functions are built
|
||||
without evaluating the raw configuration value, and an alias cannot replace
|
||||
`setlocale` or `setc`.
|
||||
- `myextip`: now uses the HTTPS `https://ipinfo.io/json` API by default, since
|
||||
the free ip-api.com tier is HTTP only (HTTPS answers 403); ipwho.is and the
|
||||
ip-api.com format are still understood, a Timezone line and the postal code
|
||||
are shown, an unexpected answer is reported instead of printing `jq` errors,
|
||||
and a non-HTTPS URL gives a warning.
|
||||
- `net.sh`: `dwl` always returned 0 (the cleanup after the transfer masked
|
||||
curl's status), so no caller ever saw a failed download; HTTP errors (404,
|
||||
403, ...) are now failures instead of saved error pages; `urlencode`
|
||||
mis-encoded accents under `LC_ALL` and now uses `%20` for spaces; `isipv4`
|
||||
leaked a variable.
|
||||
- `compress.sh`: option flags and loop variables no longer leak into the
|
||||
interactive shell.
|
||||
|
||||
|
||||
+105
-38
@@ -44,7 +44,8 @@ dwl()
|
||||
local timeout=""
|
||||
local resume=0
|
||||
|
||||
case "${DWL_DEFAULT_RESUME,,}" in
|
||||
local _res="${DWL_DEFAULT_RESUME:-}"
|
||||
case "${_res,,}" in
|
||||
1|true|yes|on)
|
||||
resume=1
|
||||
;;
|
||||
@@ -116,7 +117,9 @@ dwl()
|
||||
|
||||
_try_curl()
|
||||
{
|
||||
local args=(-sL)
|
||||
# -f: an HTTP error (404, 500, ...) is a failure, not a saved error
|
||||
# page; -S keeps error messages visible despite -s.
|
||||
local args=(-fsSL)
|
||||
[[ -n "$timeout" ]] && args+=(--max-time "$timeout" --connect-timeout "$timeout")
|
||||
if [[ -z "$output" ]]; then
|
||||
if (( resume == 1 )); then
|
||||
@@ -155,31 +158,40 @@ dwl()
|
||||
fi
|
||||
}
|
||||
|
||||
# Pick the tool: the configured one, otherwise the first available.
|
||||
# Helpers are removed on every exit path; the exit status of the transfer
|
||||
# itself is returned (the final "unset -f" must not mask it).
|
||||
local tool=""
|
||||
if [[ -n "$preferred" ]]; then
|
||||
command -v "$preferred" >/dev/null 2>&1 || {
|
||||
if ! command -v "$preferred" >/dev/null 2>&1; then
|
||||
echo "Error: preferred download tool '$preferred' is not installed." >&2
|
||||
unset -f _try_curl _try_wget _try_fetch
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
case "$preferred" in
|
||||
curl) _try_curl ;;
|
||||
wget) _try_wget ;;
|
||||
fetch) _try_fetch ;;
|
||||
curl|wget|fetch) tool="$preferred" ;;
|
||||
*)
|
||||
echo "Error: DWL_PREFERRED_TOOL '$preferred' is not supported (use curl, wget or fetch)." >&2
|
||||
unset -f _try_curl _try_wget _try_fetch
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
elif command -v curl >/dev/null 2>&1; then
|
||||
_try_curl
|
||||
tool=curl
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
_try_wget
|
||||
tool=wget
|
||||
elif command -v fetch >/dev/null 2>&1; then
|
||||
_try_fetch
|
||||
tool=fetch
|
||||
else
|
||||
echo "Error: No download utility (curl, wget, or fetch) found." >&2
|
||||
unset -f _try_curl _try_wget _try_fetch
|
||||
return 1
|
||||
fi
|
||||
|
||||
local rc=0
|
||||
"_try_${tool}" || rc=$?
|
||||
unset -f _try_curl _try_wget _try_fetch
|
||||
return "$rc"
|
||||
}
|
||||
export -f dwl
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -196,10 +208,8 @@ isipv4()
|
||||
|
||||
# Start with a regex format test (four octets)
|
||||
if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
local old_ifs=$IFS
|
||||
IFS='.'
|
||||
read -r -a ip_arr <<< "$ip"
|
||||
IFS=$old_ifs
|
||||
local -a ip_arr=()
|
||||
IFS='.' read -r -a ip_arr <<< "$ip"
|
||||
|
||||
# Ensure each octet is between 0 and 255
|
||||
local oct
|
||||
@@ -325,11 +335,16 @@ export -f isipv6
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Encode a string so it can be used as a URL parameter
|
||||
# Encode a string so it can be used in a URL path or query parameter: every
|
||||
# byte outside the unreserved set becomes %XX (UTF-8 bytes included, space is
|
||||
# %20, valid in both a path and a query).
|
||||
# Usage: urlencode <string>
|
||||
urlencode()
|
||||
{
|
||||
local LANG=C
|
||||
# LC_ALL (not LANG) is what overrides all the locale variables: with the
|
||||
# C locale each character is one byte, so multibyte characters are encoded
|
||||
# byte by byte (é -> %C3%A9) whatever the user's locale is.
|
||||
local LC_ALL=C
|
||||
local str="$*"
|
||||
local length="${#str}"
|
||||
local i
|
||||
@@ -337,7 +352,6 @@ urlencode()
|
||||
local c="${str:i:1}"
|
||||
case "$c" in
|
||||
[a-zA-Z0-9.~_-]) printf '%s' "$c" ;;
|
||||
' ') printf '+' ;;
|
||||
*) printf '%%%02X' "'$c" ;;
|
||||
esac
|
||||
done
|
||||
@@ -416,34 +430,87 @@ myextip()
|
||||
shift
|
||||
done
|
||||
|
||||
# Fetch data. Allow overriding endpoint via MYEXTIP_DEFAULT_URL config key.
|
||||
local api_url="${MYEXTIP_DEFAULT_URL:-https://ip-api.com/json/}"
|
||||
# Fetch data. The endpoint can be changed with the MYEXTIP_DEFAULT_URL
|
||||
# configuration key; the default is the HTTPS ipinfo.io JSON API (no key
|
||||
# needed). ip-api.com is not usable: its free tier is HTTP only.
|
||||
local api_url="${MYEXTIP_DEFAULT_URL:-https://ipinfo.io/json}"
|
||||
if [[ $api_url != https://* ]]; then
|
||||
disp W "The lookup service ($api_url) is not HTTPS: the answer is not protected."
|
||||
fi
|
||||
|
||||
local response
|
||||
if ! response=$(dwl "$api_url"); then
|
||||
if ! response=$(dwl -t 10 "$api_url"); then
|
||||
disp E "Failed to fetch external IP information from $api_url"
|
||||
return 2
|
||||
fi
|
||||
|
||||
# Parse with jq when available and when raw wasn't requested. The jq filter
|
||||
# is tolerant to field-name differences between providers (ip-api / ipinfo).
|
||||
if command -v jq >/dev/null 2>&1 && [[ "$show_raw" != true ]]; then
|
||||
echo "$response" | jq -r --argjson all "$all" --argjson ip "$show_ip" \
|
||||
--argjson isp "$show_isp" --argjson loc "$show_loc" \
|
||||
--argjson coord "$show_coord" --argjson as "$show_as" '
|
||||
[
|
||||
(if $all or $ip then "IP Address : \(.query // .ip)" else empty end),
|
||||
(if $all or $isp then "ISP : \(.isp // .org)" else empty end),
|
||||
(if $all or $loc then
|
||||
("Location : " + ((.city // "") + (if .city then ", " else "" end) + (if .regionName then .regionName else .region end) + (if .country then ", " + .country else "" end)))
|
||||
else empty end),
|
||||
(if $all or $coord then (if (.lat and .lon) then "Coordinates: \(.lat), \(.lon)" elif .loc then "Coordinates: \(.loc)" else empty end) else empty end),
|
||||
(if $all or $as then "AS : \(.as // .org)" else empty end)
|
||||
] | .[]'
|
||||
else
|
||||
[[ "$show_raw" != true ]] && disp W "jq is not installed, displaying raw JSON response."
|
||||
echo "$response"
|
||||
if [[ "$show_raw" == true ]]; then
|
||||
printf '%s\n' "$response"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
disp W "jq is not installed, displaying raw JSON response."
|
||||
printf '%s\n' "$response"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# The first jq stage turns the answer of any supported provider into one
|
||||
# common object, so the display stage below does not care where the data
|
||||
# comes from:
|
||||
# ipinfo.io : ip, org "AS<n> <name>", city, region, country (code),
|
||||
# loc "lat,lon", timezone (string), postal
|
||||
# ipwho.is : ip, connection{asn,org,isp}, country (name), latitude,
|
||||
# longitude, timezone{id}, postal (recognised by .connection)
|
||||
# ip-api.com: query, isp, as, regionName, lat, lon, timezone, zip
|
||||
# (recognised by .query)
|
||||
# A missing field is simply left out of the output. An answer without any
|
||||
# IP (error message, rate limit, ...) is reported as unexpected.
|
||||
local out
|
||||
out=$(printf '%s' "$response" | jq -r \
|
||||
--argjson all "$all" --argjson ip "$show_ip" \
|
||||
--argjson isp "$show_isp" --argjson loc "$show_loc" \
|
||||
--argjson coord "$show_coord" --argjson as "$show_as" '
|
||||
def clean: map(select(. != null and . != "")) ;
|
||||
(if (.connection | type) == "object" then
|
||||
{ ip: .ip,
|
||||
isp: (.connection.isp // .connection.org),
|
||||
as: (if .connection.asn then
|
||||
(["AS\(.connection.asn)", .connection.org] | clean | join(" "))
|
||||
else null end),
|
||||
city: .city, region: .region, country: .country, zip: .postal,
|
||||
lat: .latitude, lon: .longitude, tz: .timezone.id }
|
||||
elif .query then
|
||||
{ ip: .query, isp: .isp, as: .as, city: .city, region: .regionName,
|
||||
country: .country, zip: .zip, lat: .lat, lon: .lon, tz: .timezone }
|
||||
else
|
||||
((.loc // "" | split(",")) as $l
|
||||
| { ip: .ip,
|
||||
isp: (.org // null | if . then sub("^AS[0-9]+ +"; "") else . end),
|
||||
as: .org, city: .city, region: .region, country: .country,
|
||||
zip: .postal,
|
||||
lat: ($l[0] // null), lon: ($l[1] // null), tz: .timezone })
|
||||
end) as $d
|
||||
| if $d.ip == null then "unexpected response" | halt_error(3) else
|
||||
[
|
||||
(if ($all or $ip) and $d.ip then "IP Address : \($d.ip)" else empty end),
|
||||
(if ($all or $isp) and $d.isp then "ISP : \($d.isp)" else empty end),
|
||||
(if ($all or $loc) then
|
||||
([$d.city, $d.region, $d.country] | clean | join(", ")
|
||||
| select(. != "")
|
||||
| "Location : \(.)" + (if $d.zip then " (\($d.zip))" else "" end))
|
||||
else empty end),
|
||||
(if ($all or $coord) and $d.lat and $d.lon then "Coordinates : \($d.lat), \($d.lon)" else empty end),
|
||||
(if ($all or $as) and $d.as then "AS : \($d.as)" else empty end),
|
||||
(if $all and $d.tz then "Timezone : \($d.tz)" else empty end)
|
||||
] | .[]
|
||||
end' 2>/dev/null)
|
||||
local rc=$?
|
||||
if (( rc != 0 )) || [[ -z $out ]]; then
|
||||
disp E "Unexpected answer from $api_url (use --raw to see it)."
|
||||
return 3
|
||||
fi
|
||||
printf '%s\n' "$out"
|
||||
}
|
||||
export -f myextip
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user