mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2025-04-21 03:02:09 +00:00
190 lines
5.3 KiB
Bash
190 lines
5.3 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
function output_entry() {
|
|
echo "$1=$2"
|
|
}
|
|
|
|
function parse_array() {
|
|
local current_path="${1:+$1.}$2"
|
|
local current_scope="root"
|
|
local current_index=0
|
|
|
|
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
|
|
[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
|
|
preserve_current_char=0
|
|
c=${c:-' '}
|
|
|
|
case "$current_scope" in
|
|
"root") # Waiting for new object or value
|
|
case "$c" in
|
|
'{')
|
|
parse_object "$current_path" "$current_index"
|
|
current_scope="entry_separator"
|
|
;;
|
|
']')
|
|
return
|
|
;;
|
|
[\"tfTF\-0-9])
|
|
preserve_current_char=1 # Let the parse value function decide what kind of value this is
|
|
parse_value "$current_path" "$current_index"
|
|
preserve_current_char=1 # Parse value has terminated with a separator or an array end, but we can handle this only in the next while iteration
|
|
current_scope="entry_separator"
|
|
;;
|
|
|
|
esac
|
|
;;
|
|
"entry_separator")
|
|
[ "$c" == "," ] && current_index=$((current_index+1)) && current_scope="root"
|
|
[ "$c" == "]" ] && return
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
function parse_value() {
|
|
local current_path="${1:+$1.}$2"
|
|
local current_scope="root"
|
|
|
|
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
|
|
[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
|
|
preserve_current_char=0
|
|
c=${c:-' '}
|
|
|
|
case "$current_scope" in
|
|
"root") # Waiting for new string, number or boolean
|
|
case "$c" in
|
|
'"') # String begin
|
|
current_scope="string"
|
|
current_varvalue=""
|
|
;;
|
|
[\-0-9]) # Number begin
|
|
current_scope="number"
|
|
current_varvalue="$c"
|
|
;;
|
|
[tfTF]) # True or false begin
|
|
current_scope="boolean"
|
|
current_varvalue="$c"
|
|
;;
|
|
"[") # Array begin
|
|
parse_array "" "$current_path"
|
|
return
|
|
;;
|
|
"{") # Object begin
|
|
parse_object "" "$current_path"
|
|
return
|
|
esac
|
|
;;
|
|
"string") # Waiting for string end
|
|
case "$c" in
|
|
'"') # String end if not in escape mode, normal character otherwise
|
|
[ "$current_escaping" == "0" ] && output_entry "$current_path" "$current_varvalue" && return
|
|
[ "$current_escaping" == "1" ] && current_varvalue="$current_varvalue$c" && current_escaping=0
|
|
;;
|
|
'\') # Escape character, entering or leaving escape mode
|
|
current_escaping=$((1-current_escaping))
|
|
current_varvalue="$current_varvalue$c"
|
|
;;
|
|
*) # Any other string character
|
|
current_escaping=0
|
|
current_varvalue="$current_varvalue$c"
|
|
;;
|
|
esac
|
|
;;
|
|
"number") # Waiting for number end
|
|
case "$c" in
|
|
[,\]}]) # Separator or array end or object end
|
|
output_entry "$current_path" "$current_varvalue"
|
|
preserve_current_char=1 # The caller needs to handle this char
|
|
return
|
|
;;
|
|
[\-0-9.]) # Number can only contain digits, dots and a sign
|
|
current_varvalue="$current_varvalue$c"
|
|
;;
|
|
# Ignore everything else
|
|
esac
|
|
;;
|
|
"boolean") # Waiting for boolean to end
|
|
case "$c" in
|
|
[,\]}]) # Separator or array end or object end
|
|
output_entry "$current_path" "$current_varvalue"
|
|
preserve_current_char=1 # The caller needs to handle this char
|
|
return
|
|
;;
|
|
[a-zA-Z]) # No need to do some strict checking, we do not want to validate the incoming json data
|
|
current_varvalue="$current_varvalue$c"
|
|
;;
|
|
# Ignore everything else
|
|
esac
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
function parse_object() {
|
|
local current_path="${1:+$1.}$2"
|
|
local current_scope="root"
|
|
|
|
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
|
|
[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
|
|
preserve_current_char=0
|
|
c=${c:-' '}
|
|
|
|
case "$current_scope" in
|
|
"root") # Waiting for new field or object end
|
|
[ "$c" == "}" ] && return
|
|
[ "$c" == "\"" ] && current_scope="varname" && current_varname="" && current_escaping=0
|
|
;;
|
|
"varname") # Reading the field name
|
|
case "$c" in
|
|
'"') # String end if not in escape mode, normal character otherwise
|
|
[ "$current_escaping" == "0" ] && current_scope="key_value_separator"
|
|
[ "$current_escaping" == "1" ] && current_varname="$current_varname$c"
|
|
;;
|
|
'\') # Escape character, entering or leaving escape mode
|
|
current_escaping=$((1-current_escaping))
|
|
current_varname="$current_varname$c"
|
|
;;
|
|
*) # Any other string character
|
|
current_escaping=0
|
|
current_varname="$current_varname$c"
|
|
;;
|
|
esac
|
|
;;
|
|
"key_value_separator") # Waiting for the key value separator (:)
|
|
[ "$c" == ":" ] && parse_value "$current_path" "$current_varname" && current_scope="field_separator"
|
|
;;
|
|
"field_separator") # Waiting for the field separator (,)
|
|
[ "$c" == ',' ] && current_scope="root"
|
|
[ "$c" == '}' ] && return
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
function parse() {
|
|
chars_read=0
|
|
preserve_current_char=0
|
|
|
|
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
|
|
read -r -s -n 1 c
|
|
c=${c:-' '}
|
|
chars_read=$((chars_read+1))
|
|
|
|
# A valid JSON string consists of exactly one object
|
|
[ "$c" == "{" ] && parse_object "" "" && return
|
|
# ... or one array
|
|
[ "$c" == "[" ] && parse_array "" "" && return
|
|
|
|
done
|
|
}
|
|
|
|
if [ -z "$@" ]; then
|
|
INPUT=$(cat -)
|
|
else
|
|
INPUT=$(echo "$@")
|
|
fi
|
|
|
|
INPUT_LENGTH="${#INPUT}"
|
|
parse "" "" <<< "${INPUT}"
|
|
|