mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-09 21:17:03 +03:00
358 lines
9.0 KiB
Bash
358 lines
9.0 KiB
Bash
# Check if string is valid IPv4
|
|
is_ipv4() {
|
|
local ip="$1"
|
|
local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
|
|
[[ "$ip" =~ $regex ]]
|
|
}
|
|
|
|
# Check if string is valid IPv4 with CIDR mask
|
|
is_ipv4_cidr() {
|
|
local ip="$1"
|
|
local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(\/(3[0-2]|2[0-9]|1[0-9]|[0-9]))$"
|
|
[[ "$ip" =~ $regex ]]
|
|
}
|
|
|
|
is_ipv4_ip_or_ipv4_cidr() {
|
|
is_ipv4 "$1" || is_ipv4_cidr "$1"
|
|
}
|
|
|
|
is_domain() {
|
|
local str="$1"
|
|
local regex='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$'
|
|
|
|
[[ "$str" =~ $regex ]]
|
|
}
|
|
|
|
is_domain_suffix() {
|
|
local str="$1"
|
|
local normalized="${str#.}"
|
|
|
|
is_domain "$normalized"
|
|
}
|
|
|
|
# Checks if the given string is a valid base64-encoded sequence
|
|
is_base64() {
|
|
local str="$1"
|
|
|
|
if echo "$str" | base64 -d > /dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Checks if the given file exists
|
|
file_exists() {
|
|
local filepath="$1"
|
|
|
|
if [[ -f "$filepath" ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Returns the inbound tag name by appending the postfix to the given section
|
|
get_inbound_tag_by_section() {
|
|
local section="$1"
|
|
local postfix="in"
|
|
|
|
echo "$section-$postfix"
|
|
}
|
|
|
|
# Returns the outbound tag name by appending the postfix to the given section
|
|
get_outbound_tag_by_section() {
|
|
local section="$1"
|
|
local postfix="out"
|
|
|
|
echo "$section-$postfix"
|
|
}
|
|
|
|
# Constructs and returns a ruleset tag using section, name, optional type, and a fixed postfix
|
|
get_ruleset_tag() {
|
|
local section="$1"
|
|
local name="$2"
|
|
local type="$3"
|
|
local postfix="ruleset"
|
|
|
|
if [ -n "$type" ]; then
|
|
echo "$section-$name-$type-$postfix"
|
|
else
|
|
echo "$section-$name-$postfix"
|
|
fi
|
|
}
|
|
|
|
# Determines the ruleset format based on the file extension (json → source, srs → binary)
|
|
get_ruleset_format_by_file_extension() {
|
|
local file_extension="$1"
|
|
|
|
local format
|
|
case "$file_extension" in
|
|
json) format="source" ;;
|
|
srs) format="binary" ;;
|
|
*)
|
|
log "Unsupported file extension: .$file_extension"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
echo "$format"
|
|
}
|
|
|
|
# Converts a comma-separated string into a JSON array string
|
|
comma_string_to_json_array() {
|
|
local input="$1"
|
|
|
|
if [ -z "$input" ]; then
|
|
echo "[]"
|
|
return
|
|
fi
|
|
|
|
local replaced="${input//,/\",\"}"
|
|
|
|
echo "[\"$replaced\"]"
|
|
}
|
|
|
|
# Decodes a URL-encoded string
|
|
url_decode() {
|
|
local encoded="$1"
|
|
printf '%b' "$(echo "$encoded" | sed 's/+/ /g; s/%/\\x/g')"
|
|
}
|
|
|
|
# Extracts the userinfo (username[:password]) part from a URL
|
|
url_get_userinfo() {
|
|
local url="$1"
|
|
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e '/@/!d' -e 's/@.*//p'
|
|
}
|
|
|
|
# Extracts the host part from a URL
|
|
url_get_host() {
|
|
local url="$1"
|
|
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#\([:/].*\|$\)##p'
|
|
}
|
|
|
|
# Extracts the port number from a URL
|
|
url_get_port() {
|
|
local url="$1"
|
|
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#^[^/]*:\([0-9][0-9]*\).*#\1#p'
|
|
}
|
|
|
|
# Extracts the path from a URL (without query or fragment; returns "/" if empty)
|
|
url_get_path() {
|
|
local url="$1"
|
|
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*##' -e 's#\([^?]*\).*#\1#p'
|
|
}
|
|
|
|
# Extracts the value of a specific query parameter from a URL
|
|
url_get_query_param() {
|
|
local url="$1"
|
|
local param="$2"
|
|
|
|
local raw
|
|
raw=$(echo "$url" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p")
|
|
|
|
[ -z "$raw" ] && echo "" && return
|
|
|
|
echo "$raw"
|
|
}
|
|
|
|
# Extracts the basename (filename without extension) from a URL
|
|
url_get_basename() {
|
|
local url="$1"
|
|
|
|
local filename="${url##*/}"
|
|
local basename="${filename%%.*}"
|
|
|
|
echo "$basename"
|
|
}
|
|
|
|
# Extracts and returns the file extension from the given URL
|
|
url_get_file_extension() {
|
|
local url="$1"
|
|
|
|
local basename="${url##*/}"
|
|
case "$basename" in
|
|
*.*) echo "${basename##*.}" ;;
|
|
*) echo "" ;;
|
|
esac
|
|
}
|
|
|
|
# Decodes and returns a base64-encoded string
|
|
base64_decode() {
|
|
local str="$1"
|
|
local decoded_url
|
|
|
|
decoded_url="$(echo "$str" | base64 -d 2> /dev/null)"
|
|
|
|
echo "$decoded_url"
|
|
}
|
|
|
|
# Generates a unique 16-character ID based on the current timestamp and a random number
|
|
gen_id() {
|
|
printf '%s%s' "$(date +%s)" "$RANDOM" | md5sum | cut -c1-16
|
|
}
|
|
|
|
# Adds a missing UCI option with the given value if it does not exist
|
|
migration_add_new_option() {
|
|
local package="$1"
|
|
local section="$2"
|
|
local option="$3"
|
|
local value="$4"
|
|
|
|
local current
|
|
current="$(uci -q get "$package.$section.$option")"
|
|
if [ -z "$current" ]; then
|
|
log "Adding missing option '$option' with value '$value'"
|
|
uci set "$package.$section.$option=$value"
|
|
uci commit "$package"
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Migrates a configuration key in an OpenWrt config file from old_key_name to new_key_name
|
|
migration_rename_config_key() {
|
|
local config="$1"
|
|
local key_type="$2"
|
|
local old_key_name="$3"
|
|
local new_key_name="$4"
|
|
|
|
if grep -q "$key_type $old_key_name" "$config"; then
|
|
log "Deprecated $key_type found: $old_key_name migrating to $new_key_name"
|
|
sed -i "s/$key_type $old_key_name/$key_type $new_key_name/g" "$config"
|
|
fi
|
|
}
|
|
|
|
# Download URL content directly
|
|
download_to_stream() {
|
|
local url="$1"
|
|
local http_proxy_address="$2"
|
|
local retries="${3:-3}"
|
|
local wait="${4:-2}"
|
|
|
|
for attempt in $(seq 1 "$retries"); do
|
|
if [ -n "$http_proxy_address" ]; then
|
|
http_proxy="http://$http_proxy_address" https_proxy="http://$http_proxy_address" wget -qO- "$url" | sed 's/\r$//' && break
|
|
else
|
|
wget -qO- "$url" | sed 's/\r$//' && break
|
|
fi
|
|
|
|
log "Attempt $attempt/$retries to download $url failed" "warn"
|
|
sleep "$wait"
|
|
done
|
|
}
|
|
|
|
# Download URL to file
|
|
download_to_file() {
|
|
local url="$1"
|
|
local filepath="$2"
|
|
local http_proxy_address="$3"
|
|
local retries="${4:-3}"
|
|
local wait="${5:-2}"
|
|
|
|
for attempt in $(seq 1 "$retries"); do
|
|
if [ -n "$http_proxy_address" ]; then
|
|
http_proxy="http://$http_proxy_address" https_proxy="http://$http_proxy_address" wget -O "$filepath" "$url" && break
|
|
else
|
|
wget -O "$filepath" "$url" && break
|
|
fi
|
|
|
|
log "Attempt $attempt/$retries to download $url failed" "warn"
|
|
sleep "$wait"
|
|
done
|
|
|
|
if grep -q $'\r' "$filepath"; then
|
|
log "Downloaded file has Windows line endings (CRLF). Converting to Unix (LF)"
|
|
sed -i 's/\r$//' "$filepath"
|
|
fi
|
|
}
|
|
|
|
# Decompiles a sing-box SRS binary file into a JSON ruleset file
|
|
decompile_srs_file() {
|
|
local binary_filepath="$1"
|
|
local output_filepath="$2"
|
|
|
|
log "Decompiling $binary_filepath to $output_filepath" "debug"
|
|
|
|
if ! file_exists "$binary_filepath"; then
|
|
log "File $binary_filepath not found" "error"
|
|
return 1
|
|
fi
|
|
|
|
sing-box rule-set decompile "$binary_filepath" -o "$output_filepath"
|
|
if [[ $? -ne 0 ]]; then
|
|
log "Decompilation command failed for $binary_filepath" "error"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#######################################
|
|
# Parses a whitespace-separated string, validates items as either domains
|
|
# or IPv4 addresses/subnets, and returns a comma-separated string of valid items.
|
|
# Arguments:
|
|
# $1 - Input string (space-separated list of items)
|
|
# $2 - Type of validation ("domains" or "subnets")
|
|
# Outputs:
|
|
# Comma-separated string of valid domains or subnets
|
|
#######################################
|
|
parse_domain_or_subnet_string_to_commas_string() {
|
|
local string="$1"
|
|
local type="$2"
|
|
|
|
tmpfile=$(mktemp)
|
|
printf "%s\n" "$string" | sed 's/\/\/.*//' | tr ', ' '\n' | grep -v '^$' > "$tmpfile"
|
|
|
|
result="$(parse_domain_or_subnet_file_to_comma_string "$tmpfile" "$type")"
|
|
rm -f "$tmpfile"
|
|
|
|
echo "$result"
|
|
}
|
|
|
|
#######################################
|
|
# Parses a file line by line, validates entries as either domains or subnets,
|
|
# and returns a single comma-separated string of valid items.
|
|
# Arguments:
|
|
# $1 - Path to the input file
|
|
# $2 - Type of validation ("domains" or "subnets")
|
|
# Outputs:
|
|
# Comma-separated string of valid domains or subnets
|
|
#######################################
|
|
parse_domain_or_subnet_file_to_comma_string() {
|
|
local filepath="$1"
|
|
local type="$2"
|
|
|
|
local result
|
|
while IFS= read -r line; do
|
|
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
|
|
[ -z "$line" ] && continue
|
|
|
|
case "$type" in
|
|
domains)
|
|
if ! is_domain_suffix "$line"; then
|
|
log "'$line' is not a valid domain" "debug"
|
|
continue
|
|
fi
|
|
;;
|
|
subnets)
|
|
if ! is_ipv4 "$line" && ! is_ipv4_cidr "$line"; then
|
|
log "'$line' is not IPv4 or IPv4 CIDR" "debug"
|
|
continue
|
|
fi
|
|
;;
|
|
*)
|
|
log "Unknown type: $type" "error"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$result" ]; then
|
|
result="$line"
|
|
else
|
|
result="$result,$line"
|
|
fi
|
|
done < "$filepath"
|
|
|
|
echo "$result"
|
|
}
|