net bug fix, myextapi now service agnostic

This commit is contained in:
2026-10-13 13:55:24 +02:00
parent 2b4732d0e3
commit 01ec151ade
3 changed files with 117 additions and 40 deletions
+105 -38
View File
@@ -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
# ------------------------------------------------------------------------------