@@ -147,7 +147,7 @@ function ini_validate_path() {
147147 fi
148148
149149 # Check for path traversal attempts
150- if [[ " $file " =~ \.\. / ]] || [[ " $file " =~ \.\.\\ ]] ; then
150+ if [[ " $file " =~ \.\. / ]]; then
151151 ini_error " Path traversal detected: $file "
152152 return 1
153153 fi
@@ -156,18 +156,24 @@ function ini_validate_path() {
156156 local normalized
157157 if command -v realpath > /dev/null 2>&1 ; then
158158 normalized=$( realpath -m " $file " 2> /dev/null || echo " $file " )
159+ # realpath returns absolute paths, so .. in the middle is valid (e.g., /home/../home)
160+ # Only reject if .. appears at the start suggesting traversal
161+ if [[ " $normalized " =~ ^\.\. / ]]; then
162+ ini_error " Invalid path after normalization: $normalized "
163+ return 1
164+ fi
159165 else
160166 # Fallback: basic normalization
161167 normalized=" $file "
162168 # Remove leading ./ if present
163169 normalized=" ${normalized# ./ } "
164- fi
165-
166- # Additional check: ensure path doesn't escape expected boundaries
167- # This is a basic check - in production, you might want more sophisticated validation
168- if [[ " $normalized " =~ \.\. ]] ; then
169- ini_error " Invalid path after normalization: $normalized "
170- return 1
170+
171+ # Additional check: ensure path doesn't escape expected boundaries
172+ # Only check for .. if it's part of a path traversal pattern
173+ if [[ " $normalized " =~ \.\. / ]] || [[ " $normalized " =~ / \.\. ]] || [[ " $normalized " =~ ^ \.\. $ ]] ; then
174+ ini_error " Invalid path after normalization: $normalized "
175+ return 1
176+ fi
171177 fi
172178
173179 return 0
@@ -248,6 +254,24 @@ function ini_validate_env_var_name() {
248254 return 0
249255}
250256
257+ # Remove UTF-8 BOM (Byte Order Mark) from a line if present
258+ # BOM in UTF-8 is the byte sequence EF BB BF (appears as U+FEFF)
259+ # This function is safe to call on any line - it only removes BOM if present
260+ function _ini_remove_bom() {
261+ local line=" $1 "
262+
263+ # Check if line starts with UTF-8 BOM (U+FEFF = \xEF\xBB\xBF)
264+ # In bash, we check for the BOM character directly
265+ if [[ " $line " =~ ^$' \xEF\xBB\xBF ' ]]; then
266+ # Remove BOM from the beginning
267+ line=" ${line# $' \xEF\xBB\xBF ' } "
268+ ini_debug " Removed UTF-8 BOM from line"
269+ fi
270+
271+ echo " $line "
272+ }
273+
274+
251275# Lock file using flock
252276function ini_lock_file() {
253277 local file=" $1 "
@@ -380,7 +404,14 @@ function ini_read() {
380404
381405 ini_debug " Reading key '$key ' from section '$section ' in file: $file "
382406
407+ local first_line=1
383408 while IFS= read -r line; do
409+ # Remove BOM from first line if present
410+ if [ " $first_line " -eq 1 ]; then
411+ line=$( _ini_remove_bom " $line " )
412+ first_line=0
413+ fi
414+
384415 # Skip comments and empty lines
385416 if [[ -z " $line " || " $line " =~ ^[[:space:]]* [# \;] ]]; then
386417 continue
@@ -461,8 +492,20 @@ function ini_list_sections() {
461492
462493 ini_debug " Listing sections in file: $file "
463494
464- # Extract section names
465- grep -o ' ^\[[^]]*\]' " $file " 2> /dev/null | sed ' s/^\[\(.*\)\]$/\1/'
495+ # Extract section names, handling BOM on first line
496+ local first_line=1
497+ while IFS= read -r line; do
498+ # Remove BOM from first line if present
499+ if [ " $first_line " -eq 1 ]; then
500+ line=$( _ini_remove_bom " $line " )
501+ first_line=0
502+ fi
503+
504+ # Check for section header
505+ if [[ " $line " =~ ^\[ ([^]]+)\] ]]; then
506+ echo " ${BASH_REMATCH[1]} "
507+ fi
508+ done < " $file "
466509 return 0
467510}
468511
@@ -513,7 +556,14 @@ function ini_list_keys() {
513556
514557 ini_debug " Listing keys in section '$section ' in file: $file "
515558
559+ local first_line=1
516560 while IFS= read -r line; do
561+ # Remove BOM from first line if present
562+ if [ " $first_line " -eq 1 ]; then
563+ line=$( _ini_remove_bom " $line " )
564+ first_line=0
565+ fi
566+
517567 # Skip comments and empty lines
518568 if [[ -z " $line " || " $line " =~ ^[[:space:]]* [# \;] ]]; then
519569 continue
@@ -588,17 +638,30 @@ function ini_section_exists() {
588638
589639 ini_debug " Checking if section '$section ' exists in file: $file "
590640
591- # Check if section exists
592- grep -q " ^\[$escaped_section \]" " $file "
593- local result=$?
641+ # Check if section exists, handling BOM on first line
642+ local first_line=1
643+ local found=0
644+ while IFS= read -r line; do
645+ # Remove BOM from first line if present
646+ if [ " $first_line " -eq 1 ]; then
647+ line=$( _ini_remove_bom " $line " )
648+ first_line=0
649+ fi
650+
651+ # Check for section header
652+ if [[ " $line " =~ ^\[ $escaped_section \] ]]; then
653+ found=1
654+ break
655+ fi
656+ done < " $file "
594657
595- if [ $result -eq 0 ]; then
658+ if [ $found -eq 1 ]; then
596659 ini_debug " Section found: $section "
660+ return 0
597661 else
598662 ini_debug " Section not found: $section "
663+ return 1
599664 fi
600-
601- return $result
602665}
603666
604667function ini_add_section() {
@@ -697,7 +760,14 @@ function ini_write() {
697760 fi
698761
699762 # Process the file line by line
763+ local first_line=1
700764 while IFS= read -r line || [ -n " $line " ]; do
765+ # Remove BOM from first line if present
766+ if [ " $first_line " -eq 1 ]; then
767+ line=$( _ini_remove_bom " $line " )
768+ first_line=0
769+ fi
770+
701771 # Check for section
702772 if [[ " $line " =~ $section_pattern ]]; then
703773 in_section=1
@@ -826,7 +896,14 @@ function ini_remove_section() {
826896 ini_debug " Removing section '$section ' from file: $file "
827897
828898 # Process the file line by line
899+ local first_line=1
829900 while IFS= read -r line; do
901+ # Remove BOM from first line if present
902+ if [ " $first_line " -eq 1 ]; then
903+ line=$( _ini_remove_bom " $line " )
904+ first_line=0
905+ fi
906+
830907 # Check for section
831908 if [[ " $line " =~ $section_pattern ]]; then
832909 in_section=1
@@ -945,7 +1022,14 @@ function ini_remove_key() {
9451022 ini_debug " Removing key '$key ' from section '$section ' in file: $file "
9461023
9471024 # Process the file line by line
1025+ local first_line=1
9481026 while IFS= read -r line; do
1027+ # Remove BOM from first line if present
1028+ if [ " $first_line " -eq 1 ]; then
1029+ line=$( _ini_remove_bom " $line " )
1030+ first_line=0
1031+ fi
1032+
9491033 # Check for section
9501034 if [[ " $line " =~ $section_pattern ]]; then
9511035 in_section=1
@@ -1425,8 +1509,15 @@ function ini_validate() {
14251509 local in_section=0
14261510 local last_section=" "
14271511 local sections_found=0
1512+ local first_line=1
14281513
14291514 while IFS= read -r line || [ -n " $line " ]; do
1515+ # Remove BOM from first line if present
1516+ if [ " $first_line " -eq 1 ]; then
1517+ line=$( _ini_remove_bom " $line " )
1518+ first_line=0
1519+ fi
1520+
14301521 line_num=$(( line_num + 1 ))
14311522 local trimmed_line
14321523 trimmed_line=$( ini_trim " $line " )
@@ -1533,7 +1624,14 @@ function ini_get_all() {
15331624
15341625 ini_debug " Getting all keys from section '$section ' in file: $file "
15351626
1627+ local first_line=1
15361628 while IFS= read -r line; do
1629+ # Remove BOM from first line if present
1630+ if [ " $first_line " -eq 1 ]; then
1631+ line=$( _ini_remove_bom " $line " )
1632+ first_line=0
1633+ fi
1634+
15371635 # Skip comments and empty lines
15381636 if [[ -z " $line " || " $line " =~ ^[[:space:]]* [# \;] ]]; then
15391637 continue
@@ -1628,7 +1726,14 @@ function ini_rename_section() {
16281726 ini_debug " Renaming section '$old_section ' to '$new_section ' in file: $file "
16291727
16301728 # Process the file line by line
1729+ local first_line=1
16311730 while IFS= read -r line || [ -n " $line " ]; do
1731+ # Remove BOM from first line if present
1732+ if [ " $first_line " -eq 1 ]; then
1733+ line=$( _ini_remove_bom " $line " )
1734+ first_line=0
1735+ fi
1736+
16321737 # Check for old section header
16331738 if [[ " $line " =~ $old_section_pattern ]]; then
16341739 in_section=1
@@ -1781,7 +1886,14 @@ function ini_format() {
17811886 local comments_before_section=()
17821887
17831888 # First pass: collect all data
1889+ local first_line=1
17841890 while IFS= read -r line || [ -n " $line " ]; do
1891+ # Remove BOM from first line if present
1892+ if [ " $first_line " -eq 1 ]; then
1893+ line=$( _ini_remove_bom " $line " )
1894+ first_line=0
1895+ fi
1896+
17851897 local trimmed_line
17861898 trimmed_line=$( ini_trim " $line " )
17871899
0 commit comments