139 lines
5.0 KiB
Bash
Executable File
139 lines
5.0 KiB
Bash
Executable File
#!/bin/sh
|
|
# Sourced by the official entrypoint before 20-envsubst-on-templates.sh.
|
|
# Keep the normalized browser URL and CSP source identical; never interpolate raw input.
|
|
normalize_api_origin() (
|
|
set -f
|
|
export LC_ALL=C
|
|
api=$1
|
|
api=${api#"${api%%[![:space:]]*}"}
|
|
api=${api%"${api##*[![:space:]]}"}
|
|
if [ -z "$api" ]; then
|
|
return 0
|
|
fi
|
|
|
|
invalid_origin() {
|
|
echo >&2 'VITE_API_URL must be an HTTP(S) origin (optional /api), without credentials, query, or fragment'
|
|
exit 1
|
|
}
|
|
|
|
valid_ipv4() (
|
|
address=$1
|
|
case "$address" in .*|*.|*..*) return 1 ;; esac
|
|
IFS=.
|
|
set -- $address
|
|
[ "$#" -eq 4 ] || return 1
|
|
for octet do
|
|
case "$octet" in ''|*[!0-9]*|0[0-9]*) return 1 ;; esac
|
|
[ "${#octet}" -le 3 ] && [ "$octet" -le 255 ] || return 1
|
|
done
|
|
)
|
|
|
|
valid_ipv6() (
|
|
address=$1
|
|
# An embedded IPv4 address occupies two of the eight IPv6 groups.
|
|
case "$address" in
|
|
*.*)
|
|
valid_ipv4 "${address##*:}" || return 1
|
|
address=${address%:*}:0:0
|
|
;;
|
|
esac
|
|
compressed=false
|
|
case "$address" in
|
|
*:::*|*::*::*) return 1 ;;
|
|
*::*)
|
|
compressed=true
|
|
left=${address%%::*}
|
|
right=${address#*::}
|
|
case "$left" in :*) return 1 ;; esac
|
|
case "$right" in *:) return 1 ;; esac
|
|
groups=${left}${left:+:}${right}
|
|
# Removing :: can leave a trailing separator when the right side is empty.
|
|
groups=${groups%:}
|
|
;;
|
|
:*|*:) return 1 ;;
|
|
*) groups=$address ;;
|
|
esac
|
|
count=0
|
|
while [ -n "$groups" ]; do
|
|
group=${groups%%:*}
|
|
case "$group" in ''|*[!0-9a-f]*) return 1 ;; esac
|
|
[ "${#group}" -le 4 ] || return 1
|
|
count=$((count + 1))
|
|
case "$groups" in *:*) groups=${groups#*:} ;; *) groups= ;; esac
|
|
done
|
|
if [ "$compressed" = true ]; then
|
|
[ "$count" -lt 8 ]
|
|
else
|
|
[ "$count" -eq 8 ]
|
|
fi
|
|
)
|
|
|
|
# This whitelist excludes all JS/Nginx delimiters, control characters and URL credentials.
|
|
printf '%s\n' "$api" | grep -Eq '^https?://(\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9.])?)(:[0-9]{1,5})?(/|/[aA][pP][iI]/?)?$' || invalid_origin
|
|
scheme=${api%%://*}
|
|
authority=${api#*://}
|
|
authority=${authority%%/*}
|
|
authority=$(printf '%s' "$authority" | tr 'A-Z' 'a-z')
|
|
port=
|
|
case "$api" in *[![:graph:]]*) invalid_origin ;; esac
|
|
case "$authority" in
|
|
\[*\]*)
|
|
host=${authority%%]*}]
|
|
address=${host#\[}
|
|
address=${address%\]}
|
|
valid_ipv6 "$address" || invalid_origin
|
|
suffix=${authority#*]}
|
|
port=${suffix#:}
|
|
;;
|
|
*)
|
|
host=${authority%%:*}
|
|
case "$authority" in *:*) port=${authority##*:} ;; esac
|
|
case "$host" in *..*) invalid_origin ;; esac
|
|
# Numeric last labels are parsed as IPv4 by browsers, not as DNS names.
|
|
labels=${host%.}
|
|
last=${labels##*.}
|
|
case "$last" in
|
|
0x*)
|
|
case "${last#0x}" in *[!0-9a-f]*) ;; *) invalid_origin ;; esac
|
|
;;
|
|
esac
|
|
case "$last" in
|
|
*[!0-9]*)
|
|
[ "${#labels}" -le 253 ] || invalid_origin
|
|
while [ -n "$labels" ]; do
|
|
label=${labels%%.*}
|
|
case "$label" in ''|-*|*-|*[!a-z0-9-]*) invalid_origin ;; esac
|
|
[ "${#label}" -le 63 ] || invalid_origin
|
|
case "$labels" in *.*) labels=${labels#*.} ;; *) labels= ;; esac
|
|
done
|
|
;;
|
|
*) valid_ipv4 "$host" || invalid_origin ;;
|
|
esac
|
|
;;
|
|
esac
|
|
if [ -n "$port" ]; then
|
|
while [ "${port#0}" != "$port" ]; do port=${port#0}; done
|
|
[ -n "$port" ] && [ "$port" -le 65535 ] || invalid_origin
|
|
case "$scheme:$port" in http:80|https:443) port= ;; esac
|
|
fi
|
|
printf '%s://%s%s' "$scheme" "$host" "${port:+:$port}"
|
|
)
|
|
|
|
# The '-' (not ':-') distinction intentionally lets explicit empty override a build default.
|
|
API_ORIGIN=$(normalize_api_origin "${VITE_API_URL-${TG_VAULT_DEFAULT_API_URL-}}") || exit 1
|
|
PORT=${PORT:-51947}
|
|
case "$PORT" in
|
|
*[!0-9]*) echo >&2 'PORT must be an integer between 1 and 65535'; exit 1 ;;
|
|
esac
|
|
while [ "${PORT#0}" != "$PORT" ]; do PORT=${PORT#0}; done
|
|
if [ -z "$PORT" ] || [ "${#PORT}" -gt 5 ] || [ "$PORT" -gt 65535 ]; then
|
|
echo >&2 'PORT must be an integer between 1 and 65535'
|
|
exit 1
|
|
fi
|
|
export API_ORIGIN PORT
|
|
# Preserve $host, $uri, $http_range, and every other Nginx request variable.
|
|
NGINX_ENVSUBST_FILTER='^(PORT|API_ORIGIN)$'
|
|
export NGINX_ENVSUBST_FILTER
|
|
printf 'window.__TG_VAULT_CONFIG__ = { API_URL: "%s" };\n' "$API_ORIGIN" > /usr/share/nginx/html/runtime-config.js
|
|
chmod 644 /usr/share/nginx/html/runtime-config.js
|