Skip to content

Instantly share code, notes, and snippets.

@Guilhem7
Last active July 1, 2025 22:05
Show Gist options
  • Select an option

  • Save Guilhem7/bedc05a48a99c16c67e3479a1ccf028a to your computer and use it in GitHub Desktop.

Select an option

Save Guilhem7/bedc05a48a99c16c67e3479a1ccf028a to your computer and use it in GitHub Desktop.
Parse a tomcat web.xml file in order to see which servlet is used for which urls, and which filter(s) are processed on it
#!/bin/bash
set -e
## Global var
SCRIPT_NAME="${0##*/}"
## Style
RED='\e[31m'
GREEN='\e[32m'
BLUE='\e[34m'
YELLOW='\e[93m'
BOLD='\e[1m'
DIM='\e[2m'
ITALIC='\e[3m'
N='\e[0m'
## Util function
die(){
if [ -n "$BASH_LINENO" ];then
err "${SCRIPT_NAME}: ${BOLD}Error:Line:${BASH_LINENO[0]}${N} $*" >&2
else
err "${SCRIPT_NAME}: ${BOLD}Error${N} $*" >&2
fi
exit 1
}
msg(){
echo -e "${GREEN}[+]${N} $*"
}
info(){
echo -e "${BLUE}[-]${N} $*"
}
err(){
echo -e "${RED}[x]${N} $*" >&2
}
show_help() {
echo "Usage: $SCRIPT_NAME [--xml XML] [-h] [-v]"
echo
echo "Options:"
echo " --xml XML Specify the path to web.xml (default web.xml)"
echo " -v, --verbose Increase verbosity"
echo " -h, --help Display this help message"
}
show_progress() {
local width=50
local percent=$(( 100 * $1 / $2 ))
local filled=$(( width * $1 / $2 ))
local empty=$(( width - filled ))
local fill_char=$(printf " %.0s" {1..101})
printf "\r["
printf "\e[47m"
printf "%.*s" "$filled" "$fill_char"
printf "\e[0m"
printf "%.*s" "$empty" "$fill_char"
printf "] %3d%% (%d/%d)" "$percent" "$1" "$2"
if [ "$1" -eq "$2" ]; then
echo "" # move to next line when complete
fi
# Show progress arg 1 is count and arg 2 is the total
# echo -en "\r${BLUE}[-]${N} Progress ${DIM}$percent${N}/${BOLD}100%${N}\e[K"
# echo -en "\r[] $1/$2\e[K"
}
if ! command -v xq 2&>/dev/null;then
err "Command xq not found, please run: ${BOLD}pip3 install yq${N}"
fi
if ! command -v jq 2&>/dev/null;then
err "Command jq not found, please run: ${BOLD}sudo apt install -y jq${N}"
fi
WEB_XML="web.xml"
VERBOSE=0
while [[ "$#" -gt 0 ]]; do
case "$1" in
-h)
show_help
exit 0
;;
--xml)
if [[ -n "$2" && "$2" != -* ]]; then
WEB_XML="$2"
shift
else
echo "Error: --xml requires valid xml"
exit 1
fi
;;
-v|--verbose)
VERBOSE=1
;;
*)
echo "Unknown option: $1"
show_help
exit 1
;;
esac
shift
done
if [ ! -f "$WEB_XML" ];then
err "File not found: $WEB_XML"
exit 1
fi
if [ "$VERBOSE" -eq 1 ];then
info "Processing $WEB_XML"
info "Recovering ${GREEN}servlets${N} ${ITALIC}${DIM}(can take some time)${N}"
fi
# Parse all servlet mappings into a lookup: servlet-name -> list of url-patterns
declare -A servlet_class_map
declare -A servlet_urls_map
xq_output=$(xq -c '
.["web-app"]["servlet"][] as $s
| {
name: $s["servlet-name"],
class: $s["servlet-class"],
urls: (
.["web-app"]["servlet-mapping"]
| map(select(.["servlet-name"] == $s["servlet-name"])["url-pattern"])
| flatten
)
}
' $WEB_XML)
count=1
total_lines=$(printf "%s\n" "$xq_output" |wc -l)
while IFS= read -r entry; do
if [ "$VERBOSE" -eq 1 ];then
show_progress "$count" "$total_lines"
let "count=count+1"
fi
name=$(jq -r '.name' <<< "$entry")
class=$(jq -r '.class' <<< "$entry")
urls=$(jq -r '.urls[]' <<< "$entry" | paste -sd "|" -)
servlet_class_map["$name"]="$class"
servlet_urls_map["$name"]="$urls"
done < <(printf "%s\n" "$xq_output")
if [ "$VERBOSE" -eq 1 ];then
info "Recovering ${GREEN}filters${N}"
fi
# Parse all filters and map filter-name to list of patterns
declare -A filter_class_map
declare -A filter_patterns_map
xq_output=$(xq -c '
.["web-app"]["filter"][] as $f
| {
name: $f["filter-name"],
class: $f["filter-class"],
patterns: (
.["web-app"]["filter-mapping"]
| map(select(.["filter-name"] == $f["filter-name"])["url-pattern"])
| flatten
)
}
' $WEB_XML)
count=1
total_lines=$(printf "%s\n" "$xq_output" |wc -l)
while IFS= read -r entry; do
if [ "$VERBOSE" -eq 1 ];then
show_progress "$count" "$total_lines"
let "count=count+1"
fi
name=$(jq -r '.name' <<< "$entry")
class=$(jq -r '.class' <<< "$entry")
patterns=$(jq -r '.patterns[]' <<< "$entry" | paste -sd "|" -)
filter_class_map["$name"]="$class"
filter_patterns_map["$name"]="$patterns"
done < <(printf "%s\n" "$xq_output")
# Process each servlet
for servlet in "${!servlet_class_map[@]}"; do
class="${servlet_class_map[$servlet]}"
IFS='|' read -ra urls <<< "${servlet_urls_map[$servlet]}"
declare -A filter_set=()
# Match servlet URLs with all filter patterns
for filter in "${!filter_patterns_map[@]}"; do
IFS='|' read -ra patterns <<< "${filter_patterns_map[$filter]}"
for u in "${urls[@]}"; do
for p in "${patterns[@]}"; do
regex="^${p//\*/.*}$"
if [[ "$u" =~ $regex ]]; then
filter_set["$filter"]=1
fi
done
done
done
# Output result
echo -e "Servlet : ${GREEN}${servlet}${N}"
echo -e " Class : ${BOLD}${class}${N}"
echo -e " URLs :"
for u in "${urls[@]}"; do echo " - $u"; done
echo " Filters:"
if [[ ${#filter_set[@]} -eq 0 ]]; then
echo " (none)"
else
for f in "${!filter_set[@]}"; do
echo " - $f"
done
fi
echo
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment