mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-31 19:00:54 +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}"
 | |
| 
 |