@@ -233,6 +233,71 @@ function initialize_variables {
233233 export flac2mp3_keep=0
234234 export flac2mp3_type=$( printenv | sed -n ' s/_eventtype *=.*$//p' )
235235}
236+ function parse_arg_string {
237+ # Safely parse a shell-style argument string without eval
238+ local input=" $1 "
239+
240+ export flac2mp3_arg_array=()
241+ local char
242+ local token=" "
243+ local in_single=false
244+ local in_double=false
245+ local escaped=false
246+ local i=0
247+ local len=${# input}
248+
249+ while [ $i -lt " $len " ]; do
250+ char=${input: i: 1}
251+ if $escaped ; then
252+ token+=" $char "
253+ escaped=false
254+ elif [ " $char " = " \\ " ]; then
255+ escaped=true
256+ elif $in_single ; then
257+ if [ " $char " = " '" ]; then
258+ in_single=false
259+ else
260+ token+=" $char "
261+ fi
262+ elif $in_double ; then
263+ if [ " $char " = ' "' ]; then
264+ in_double=false
265+ elif [ " $char " = " \\ " ]; then
266+ escaped=true
267+ else
268+ token+=" $char "
269+ fi
270+ else
271+ case " $char " in
272+ [[:space:]] )
273+ if [ -n " $token " ]; then
274+ flac2mp3_arg_array+=(" $token " )
275+ token=" "
276+ fi
277+ ;;
278+ " '" )
279+ in_single=true
280+ ;;
281+ ' "' )
282+ in_double=true
283+ ;;
284+ * )
285+ token+=" $char "
286+ ;;
287+ esac
288+ fi
289+ i=$(( i + 1 ))
290+ done
291+
292+ if [ -n " $token " ]; then
293+ flac2mp3_arg_array+=(" $token " )
294+ fi
295+
296+ if $escaped || $in_single || $in_double ; then
297+ return 1
298+ fi
299+ return 0
300+ }
236301function process_command_line {
237302 # Process arguments, either from the command line or from the environment variable
238303
@@ -246,9 +311,14 @@ function process_command_line {
246311 if [ $# -ne 0 ]; then
247312 export flac2mp3_prelogmessage=" Warning|FLAC2MP3_ARGS environment variable set but will be ignored because command line arguments were also specified."
248313 else
249- # Move the environment variable arguments to the command line for processing
314+ # Move the environment variable arguments to the command line for processing using a safe parser
250315 export flac2mp3_prelogmessage=" Info|Using settings from environment variable."
251- eval set -- " $FLAC2MP3_ARGS "
316+ if ! parse_arg_string " $FLAC2MP3_ARGS " ; then
317+ echo_ansi " Error|Invalid quoting in FLAC2MP3_ARGS environment variable." >&2
318+ usage
319+ exit 3
320+ fi
321+ set -- " ${flac2mp3_arg_array[@]} "
252322 fi
253323 fi
254324
@@ -567,7 +637,7 @@ function get_version {
567637 # Get Lidarr version
568638
569639 call_api 0 " Getting ${flac2mp3_type^} version." " GET" " system/status"
570- local json_test=" $( echo $flac2mp3_result | jq -crM ' .version?' ) "
640+ local json_test=" $( echo " $flac2mp3_result " | jq -crM ' .version?' ) "
571641 [ " $json_test " != " null" ] && [ " $json_test " != " " ]
572642 return
573643}
@@ -576,7 +646,7 @@ function get_trackfile_info {
576646
577647 # shellcheck disable=SC2154
578648 call_api 0 " Getting track file info for album id $lidarr_album_id ." " GET" " trackFile" " albumId=$lidarr_album_id "
579- local json_test=" $( echo $flac2mp3_result | jq -crM ' .[].id?' ) "
649+ local json_test=" $( echo " $flac2mp3_result " | jq -crM ' .[].id?' ) "
580650 [ " $json_test " != " null" ] && [ " $json_test " != " " ]
581651 return
582652}
@@ -601,7 +671,7 @@ function check_job {
601671 }
602672
603673 # Job status checks
604- local json_test=" $( echo $flac2mp3_result | jq -crM ' .status?' ) "
674+ local json_test=" $( echo " $flac2mp3_result " | jq -crM ' .status?' ) "
605675 case " $json_test " in
606676 completed) local return=0; break ;;
607677 failed) local return=2; break ;;
@@ -648,15 +718,15 @@ function get_import_info {
648718
649719 # shellcheck disable=SC2154
650720 call_api 1 " Getting list of files that can be imported." " GET" " manualimport" " artistId=$lidarr_artist_id " " folder=$lidarr_artist_path " " filterExistingFiles=true" " replaceExistingFiles=false"
651- local json_test=" $( echo $flac2mp3_result | jq -crM ' .[]? | .tracks?' ) "
721+ local json_test=" $( echo " $flac2mp3_result " | jq -crM ' .[]? | .tracks?' ) "
652722 [ " $json_test " != " null" ] && [ " $json_test " != " " ] && [ " $json_test " != " []" ]
653723 return
654724}
655725function import_tracks {
656726 # Import new track into Lidarr
657727
658728 call_api 0 " Importing $flac2mp3_import_count new files into ${flac2mp3_type^} ." " POST" " command" " {\" name\" :\" ManualImport\" ,\" files\" :$flac2mp3_json ,\" importMode\" :\" auto\" ,\" replaceExistingFiles\" :false}"
659- local json_test=" $( echo $flac2mp3_result | jq -crM ' .id?' ) "
729+ local json_test=" $( echo " $flac2mp3_result " | jq -crM ' .id?' ) "
660730 [ " $json_test " != " null" ] && [ " $json_test " != " " ]
661731 return
662732}
@@ -666,7 +736,7 @@ function ffprobe {
666736 local trackfile=" $1 " # Track file to inspect
667737
668738 local ffcommand=" /usr/bin/ffprobe"
669- execute_ff_command " inspecting file: '$trackfile '" " $ffcommand " -hide_banner -loglevel $flac2mp3_ffmpeg_log -print_format json=compact=1 -show_format -show_entries " format=tags : format_tags=title,disc,genre" -i " $trackfile "
739+ execute_ff_command " inspecting file: '$trackfile '" " $ffcommand " -hide_banner -loglevel " $flac2mp3_ffmpeg_log " -print_format json=compact=1 -show_format -show_entries " format=tags : format_tags=title,disc,genre" -i " $trackfile "
670740
671741 unset flac2mp3_ffprobe_json
672742 declare -g flac2mp3_ffprobe_json
@@ -697,8 +767,8 @@ function check_log {
697767 # Log file checks
698768
699769 # Check that log path exists
700- if [ ! -d " $( dirname $flac2mp3_log ) " ]; then
701- [ $flac2mp3_debug -ge 1 ] && echo_ansi " Debug|Log file path does not exist: '$( dirname $flac2mp3_log ) '. Using log file in current directory."
770+ if [ ! -d " $( dirname -- " $flac2mp3_log " ) " ]; then
771+ [ $flac2mp3_debug -ge 1 ] && echo_ansi " Debug|Log file path does not exist: '$( dirname -- " $flac2mp3_log " ) '. Using log file in current directory."
702772 export flac2mp3_log=./flac2mp3.txt
703773 fi
704774
@@ -871,7 +941,7 @@ function check_config_file {
871941 echo_ansi " $message " >&2
872942 end_script 17
873943 }
874- export flac2mp3_version=" $( echo $flac2mp3_result | jq -crM .version) "
944+ export flac2mp3_version=" $( echo " $flac2mp3_result " | jq -crM .version) "
875945 [ $flac2mp3_debug -ge 1 ] && echo " Debug|Detected ${flac2mp3_type^} version $flac2mp3_version " | log
876946
877947 # Import mode will not have any audio tracks until after import
@@ -980,7 +1050,7 @@ function call_api {
9801050 # If database is locked, log and loop
9811051 if wait_if_locked; then
9821052 if [ $curl_return -ne 0 ]; then
983- local error_message=" $( echo $flac2mp3_result | jq -jcM ' if type=="array" then map(.errorMessage) | join(", ") else (if has("title") then "[HTTP \(.status?)] \(.title) \(.errors?)" elif has("message") then .message else "Unknown JSON format." end) end' ) "
1053+ local error_message=" $( echo " $flac2mp3_result " | jq -jcM ' if type=="array" then map(.errorMessage) | join(", ") else (if has("title") then "[HTTP \(.status?)] \(.title) \(.errors?)" elif has("message") then .message else "Unknown JSON format." end) end' ) "
9841054 local message=$( echo -e " [$curl_return ] curl error when calling: \" $url \" $data_info \nWeb server returned: $error_message " | awk ' {print "Error|"$0}' )
9851055 echo " $message " | log
9861056 echo_ansi " $message " >&2
@@ -1006,7 +1076,7 @@ function wait_if_locked {
10061076 # 0 - Database is not locked
10071077 # 1 - Database is locked
10081078
1009- if [[ " $( echo $flac2mp3_result | jq -jcM ' .message?' ) " =~ database\ is\ locked ]]; then
1079+ if [[ " $( echo " $flac2mp3_result " | jq -jcM ' .message?' ) " =~ database\ is\ locked ]]; then
10101080 local return=1
10111081 echo " Warn|Database is locked; system is likely overloaded. Sleeping 1 minute." | log
10121082 sleep 60
@@ -1053,7 +1123,7 @@ function get_media_config {
10531123 # Get media management configuration
10541124
10551125 call_api 0 " Getting ${flac2mp3_type^} configuration." " GET" " config/mediamanagement"
1056- local json_test=" $( echo $flac2mp3_result | jq -crM ' .id?' ) "
1126+ local json_test=" $( echo " $flac2mp3_result " | jq -crM ' .id?' ) "
10571127 [ " $json_test " != " null" ] && [ " $json_test " != " " ]
10581128 return
10591129}
@@ -1097,20 +1167,30 @@ function set_ffmpeg_parameters {
10971167 2) export flac2mp3_ffmpeg_log=" info" ;;
10981168 * ) export flac2mp3_ffmpeg_log=" debug" ;;
10991169 esac
1170+
1171+ local -a brCommand=()
1172+ flac2mp3_ffmpeg_opts=()
1173+
11001174 if [ -n " $flac2mp3_bitrate " ]; then
11011175 [ $flac2mp3_debug -ge 1 ] && echo " Debug|Using constant bitrate of $flac2mp3_bitrate " | log
1102- local brCommand=" -b:a $flac2mp3_bitrate "
1176+ brCommand=( -b:a " $flac2mp3_bitrate " )
11031177 elif [ -n " $flac2mp3_vbrquality " ]; then
11041178 [ $flac2mp3_debug -ge 1 ] && echo " Debug|Using variable quality of $flac2mp3_vbrquality " | log
1105- brCommand=" -q:a $flac2mp3_vbrquality "
1179+ brCommand=( -q:a " $flac2mp3_vbrquality " )
11061180 elif [ -n " $flac2mp3_ffmpegadv " ]; then
11071181 [ $flac2mp3_debug -ge 1 ] && echo " Debug|Using advanced ffmpeg options \" $flac2mp3_ffmpegadv \" " | log
11081182 [ $flac2mp3_debug -ge 1 ] && echo " Debug|Exporting with file extension \" $flac2mp3_extension \" " | log
1109- export flac2mp3_ffmpeg_opts=" $flac2mp3_ffmpegadv "
1183+ if ! parse_arg_string " $flac2mp3_ffmpegadv " ; then
1184+ echo_ansi " Error|Invalid quoting in advanced ffmpeg options." >&2
1185+ usage
1186+ exit 3
1187+ fi
1188+ flac2mp3_ffmpeg_opts=(" ${flac2mp3_arg_array[@]} " )
11101189 fi
11111190
1112- # Set default ffmpeg options
1113- [ -z " $flac2mp3_ffmpeg_opts " ] && export flac2mp3_ffmpeg_opts=" -c:v copy -map 0 -y -acodec libmp3lame ${brCommand} -write_id3v1 1 -id3v2_version 3"
1191+ if [ ${# flac2mp3_ffmpeg_opts[@]} -eq 0 ]; then
1192+ flac2mp3_ffmpeg_opts=(" -c:v" " copy" " -map" " 0" " -y" " -acodec" " libmp3lame" " ${brCommand[@]} " " -write_id3v1" " 1" " -id3v2_version" " 3" )
1193+ fi
11141194}
11151195function process_tracks {
11161196 # Process tracks loop
@@ -1177,7 +1257,10 @@ function process_tracks {
11771257
11781258 # Get track metadata
11791259 if ffprobe " $track " ; then
1180- for tag in $( echo $flac2mp3_tags | tr ' ,' ' |' ) ; do
1260+ local IFS=' ,'
1261+ local -a flac2mp3_tag_array=()
1262+ read -r -a flac2mp3_tag_array <<< " $flac2mp3_tags"
1263+ for tag in " ${flac2mp3_tag_array[@]} " ; do
11811264 # shellcheck disable=SC2089
11821265 case " $tag " in
11831266 title )
@@ -1229,7 +1312,7 @@ function process_tracks {
12291312 local ffcommand=" nice /usr/bin/ffmpeg"
12301313 local IFS=$' \t\n ' # Temporarily restore IFS
12311314 # shellcheck disable=SC2090
1232- execute_ff_command " converting track: '$track ' to '$tempTrack '" " $ffcommand " -loglevel $flac2mp3_ffmpeg_log -nostdin -i " $track " $ flac2mp3_ffmpeg_opts " ${metadata[@]} " " $tempTrack "
1315+ execute_ff_command " converting track: '$track ' to '$tempTrack '" " $ffcommand " -loglevel " $flac2mp3_ffmpeg_log " -nostdin -i " $track " " ${ flac2mp3_ffmpeg_opts[@]} " " ${metadata[@]} " " $tempTrack "
12331316 local return=$? ; [ $return -ne 0 ] && {
12341317 change_exit_status 13
12351318 # Delete the temporary file if it exists
@@ -1295,7 +1378,7 @@ function process_tracks {
12951378 }
12961379 else
12971380 # Call Lidarr to delete the original file, or recycle if configured.
1298- local track_id=$( echo $flac2mp3_trackfiles | jq -crM " .[] | select(.path == \" $track \" ) | .id" )
1381+ local track_id=$( echo " $flac2mp3_trackfiles " | jq -crM " .[] | select(.path == \" $track \" ) | .id" )
12991382 delete_track $track_id
13001383 local return=$? ; [ $return -ne 0 ] && {
13011384 local message=" Error|[$return ] ${flac2mp3_type^} error when deleting the original track: '$track '. Not importing new track into ${flac2mp3_type^} ."
@@ -1381,7 +1464,7 @@ function update_database {
13811464 # Build JSON data for all tracks
13821465 # NOTE: Tracks with empty track IDs will not appear in the resulting JSON and will therefore not be imported into Lidarr
13831466 [ $flac2mp3_debug -ge 1 ] && echo " Debug|Building JSON data to import" | log
1384- export flac2mp3_json=$( echo $flac2mp3_result | jq -jcM "
1467+ export flac2mp3_json=$( echo " $flac2mp3_result " | jq -jcM "
13851468 map(
13861469 select(.path | inside(\" $flac2mp3_import_list \" )) |
13871470 {path, \" artistId\" :$lidarr_artist_id , \" albumId\" :$lidarr_album_id , albumReleaseId,\" trackIds\" :[.tracks[].id], quality, \" disableReleaseSwitching\" :false}
@@ -1396,7 +1479,7 @@ function update_database {
13961479 echo_ansi " $message " >&2
13971480 change_exit_status 17
13981481 }
1399- local jobid=" $( echo $flac2mp3_result | jq -crM .id) "
1482+ local jobid=$( echo " $flac2mp3_result " | jq -crM .id)
14001483
14011484 # Check status of job (see issue #39)
14021485 check_job $jobid
@@ -1407,7 +1490,7 @@ function update_database {
14071490 2) local message=" Warn|${flac2mp3_type^} job ID $jobid failed."
14081491 change_exit_status 17
14091492 ;;
1410- 3) local message=" Warn|Script timed out waiting on ${flac2mp3_type^} job ID $jobid . Last status was: $( echo $flac2mp3_result | jq -crM .status) "
1493+ 3) local message=" Warn|Script timed out waiting on ${flac2mp3_type^} job ID $jobid . Last status was: $( echo " $flac2mp3_result " | jq -crM .status) "
14111494 change_exit_status 18
14121495 ;;
14131496 10) local message=" Error|${flac2mp3_type^} job ID $jobid returned a curl error."
0 commit comments