setCommentsPre($coms); $doc->addSection($sec); $dir = null; $coms = array(); $state = self::LINE_END; $token = ''; } break; case self::DIRECTIVE_KEY: if ($s !== '=') { $token .= $s; } else { $dir = new Directive($token); $dir->setCommentsPre($coms); if (isset($sec)) { $sec->addDirective($dir); } else { Logger::warning(sprintf( 'Ini parser warning: section-less directive "%s" ignored. (l. %d)', $token, $line )); } $coms = array(); $state = self::DIRECTIVE_VALUE_START; $token = ''; } break; case self::DIRECTIVE_VALUE_START: if (ctype_space($s)) { continue 2; } elseif ($s === '"') { $state = self::DIRECTIVE_VALUE_QUOTED; } else { $state = self::DIRECTIVE_VALUE; $token = $s; } break; case self::DIRECTIVE_VALUE: /* Escaping non-quoted values is not supported by php_parse_ini, it might be reasonable to include in case we are switching completely our own parser implementation */ if ($s === "\n" || $s === ";") { $dir->setValue($token); $token = ''; if ($s === "\n") { $state = self::LINE_START; $line ++; } elseif ($s === ';') { $state = self::COMMENT; } } else { $token .= $s; } break; case self::DIRECTIVE_VALUE_QUOTED: if ($s === '\\') { $state = self::ESCAPE; $escaping = self::DIRECTIVE_VALUE_QUOTED; } elseif ($s !== '"') { $token .= $s; } else { $dir->setValue($token); $token = ''; $state = self::LINE_END; } break; case self::COMMENT: case self::COMMENT_END: if ($s !== "\n") { $token .= $s; } else { $com = new Comment(); $com->setContent($token); $token = ''; // Comments at the line end belong to the current line's directive or section. Comments // on empty lines belong to the next directive that shows up. if ($state === self::COMMENT_END) { if (isset($dir)) { $dir->setCommentPost($com); } else { $sec->setCommentPost($com); } } else { $coms[] = $com; } $state = self::LINE_START; $line ++; } break; case self::LINE_END: if ($s === "\n") { $state = self::LINE_START; $line ++; } elseif ($s === ';') { $state = self::COMMENT_END; } break; } } // process the last token switch ($state) { case self::COMMENT: case self::COMMENT_END: $com = new Comment(); $com->setContent($token); if ($state === self::COMMENT_END) { if (isset($dir)) { $dir->setCommentPost($com); } else { $sec->setCommentPost($com); } } else { $coms[] = $com; } break; case self::DIRECTIVE_VALUE: $dir->setValue($token); $sec->addDirective($dir); break; case self::ESCAPE: case self::DIRECTIVE_VALUE_QUOTED: case self::DIRECTIVE_KEY: case self::SECTION: self::throwParseError('File ended in unterminated state ' . $state, $line); } if (! empty($coms)) { $doc->setCommentsDangling($coms); } return $doc; } /** * Read the ini file and parse it with ::parseIni() * * @param string $file The ini file to read * * @return Config * @throws NotReadableError When the file cannot be read */ public static function parseIniFile($file) { if (($path = realpath($file)) === false) { throw new NotReadableError('Couldn\'t compute the absolute path of `%s\'', $file); } if (($content = file_get_contents($path)) === false) { throw new NotReadableError('Couldn\'t read the file `%s\'', $path); } try { $configArray = parse_ini_string($content, true, INI_SCANNER_RAW); } catch (ErrorException $e) { throw new ConfigurationError('Couldn\'t parse the INI file `%s\'', $path, $e); } $unescaped = array(); foreach ($configArray as $section => $options) { $unescaped[self::unescapeSectionName($section)] = array_map([__CLASS__, 'unescapeOptionValue'], $options); } return Config::fromArray($unescaped)->setConfigFile($file); } /** * Unescape significant characters in the given section name * * @param string $str * * @return string */ protected static function unescapeSectionName($str) { $str = str_replace('\"', '"', $str); $str = str_replace('\;', ';', $str); return str_replace('\\\\', '\\', $str); } /** * Unescape significant characters in the given option value * * @param string $str * * @return string */ protected static function unescapeOptionValue($str) { $str = str_replace('\n', "\n", $str); $str = str_replace('\r', "\r", $str); $str = str_replace('\"', '"', $str); $str = str_replace('\\\\', '\\', $str); // This replacement is a work-around for PHP bug #76965. Fixed with versions 7.1.24, 7.2.12 and 7.3.0. return preg_replace('~^([\'"])(.*?)\1\s+$~', '$2', $str); } }