# Functions for parsing control files # # Copyright (C) 2012, 2018, 2019 Patrick McDermott # # This file is part of opkbuild. # # opkbuild is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # opkbuild is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with opkbuild. If not, see . _OB_SUBSTVARS_MAX_DEPTH=50 _ob_parse_control_error() { file="${1}" line_nr="${2}" msg_id="${3}" shift 3 || _ob_abort local file_info= if [ ${line_nr} -eq 0 ]; then file_info="$(printf '%s' "${file}")" else file_info="$(printf '%s(l%d)' "${file}" "${line_nr}")" fi _ob_warn_msg "${msg_id}" "${file_info}" "${@}" return 0 } ## @brief Parse a control file ## @details \fBob_parse_control\fP() parses a control file of field names and ## values formatted like RFC 822 (or RFC 2822 or RFC 5322) headers. ## For each field, \fBob_parse_control\fP calls \fIfield_cb\fP with the ## field name, the field value, and \fIuser_data\fP as arguments. If ## \fIreq_fields\fP and \fIopt_fields\fP are given, ## \fBob_parse_control\fP verifies that the input control file contains ## all fields listed in \fIreq_fields\fP and no fields that are listed ## in neither \fIreq_fields\fP nor \fIopt_fields\fP. If \fIfield_cb\fP ## returns non-zero, \fBob_parse_control\fP stops parsing and ## immediately returns, without verifying that all required fields were ## found. ## @operand file req The control file to parse, or "-" for standard input. ## @operand field_cb req Callback to run for each field. Must accept three ## arguments: the field name, the field value, and ## \fIuser_data\fP. ## @operand user_data req Data to pass to \fIfield_cb\fP. ## @operand req_fields opt Required fields that must appear in the control file. ## @operand opt_fields opt Optional fields that may appear in the control file. ## @return Returns 0 after parsing. ## @stderr Prints error messages on parse errors. ## @pure maybe This function has no side effects. Whether this function is ## subshell-safe in practice depends on whether \fIfield_cb\fP is ## subshell-safe. ob_parse_control() { local file="${1}" local field_cb="${2}" local user_data="${3}" shift 3 || _ob_abort local check_fields= local req_fields= local opt_fields= local got_fields= local line_nr= local line= local name= local value= local sep= check_fields='false' if [ ${#} -eq 2 ]; then req_fields=" ${1} " opt_fields=" ${2} " check_fields='true' fi got_fields=' ' line_nr=0 while IFS='' read -r line; do line_nr=$((${line_nr} + 1)) case "${line}" in '') _ob_parse_control_error "${file}" "${line_nr}" \ 'control_empty_line' ;; '#'*) # Comment. ;; [!\ ]*':'*) # "Name: Value" line. if [ -n "${name}" ]; then if ! "${field_cb}" "${name}" "${value}"\ "${user_data}"; then return 0 fi fi IFS=': ' read name value <<-EOF ${line} EOF if [ -z "${name}" ]; then # Badly formatted control field. _ob_parse_control_error "${file}" \ "${line_nr}" 'control_bad_nv' continue fi case "${got_fields}" in *" ${name} "*) # Duplicate field. _ob_parse_control_error \ "${file}" "${line_nr}" \ 'control_duplicate_field' \ "${name}" continue esac got_fields="${got_fields}${name} " case "${req_fields}" in *" ${name} "*) # Required field: remove from list. req_fields="${req_fields% ${name} *}$(:\ ) ${req_fields#* ${name} }" continue esac if ${check_fields}; then case "${opt_fields}" in *" ${name} "*) # Optional field. continue esac # Unknown field. _ob_parse_control_error \ "${file}" "${line_nr}" \ 'control_unknown_field' \ "${name}" fi ;; ' '*) # Continuation line. if [ -z "${name}" ]; then # Expecting a "Name: Value" line. _ob_parse_control_error "${file}" \ "${line_nr}" \ 'control_found_continuation' continue fi value="${value}${OB_LF}${line# }" ;; esac done <<-EOF $(cat -- "${file}") EOF if [ -n "${name}" ]; then if ! "${field_cb}" "${name}" "${value}" "${user_data}"; then return 0 fi fi if ${check_fields}; then case "${req_fields}" in *[!\ ]*) # Missing required control fields. sep="$(_ob_get_msg 'list_item_separator')" req_fields="$(printf "%s${sep}" ${req_fields}; \ printf 'x')" req_fields="${req_fields%${sep}x}" _ob_parse_control_error "${file}" '0' \ 'control_missing_fields' "${req_fields}" esac fi return 0 } ## @brief Set a substitution variable ## @details \fBob_set_substvar\fP() sets a substitution variable for later use ## by \fBob_substvars\fP(3). ## @operand name req The name of the substitution variable. May only consist ## of uppercase and lowercase Latin letters, digits, and ## hyphens and must be at least one character long. ## @operand value req The value of the substitution variable. ## @return Returns 0 on success, or 1 if \fIname\fP is empty or contains invalid ## characters. ## @pure no This function sets an internal global variable. ob_set_substvar() { local name="${1}" local value="${2}" shift 2 || _ob_abort # Convert variable name to uppercase and validate. case "${name}" in *[!A-Za-z0-9-]* | '') return 1 esac name="$(tr 'a-z-' 'A-Z_' <<-EOF ${name} EOF )" # Trim leading and trailing whitespace from value. value="$(sed -n ' H; # Store each input line in the hold space. ${ # At the last line of input: g; # restore the hold space into the pattern space, s/^[\n]*//; # remove leading newline characters, s/[\n]*$//; # remove trailing newline characters, and p; # print the results. }; ' <<-EOF ${value} EOF )" eval "_OB_SUBSTVAR_${name}=\"\${value}\"" return 0 } ## @brief Substitute variables in text ## @details \fBob_substvars\fP() substitutes variables previously set with ## \fBob_set_substvar\fP(3) in a string. The format for variable ## substitutions is \fI${var}\fP, and substitutions can be nested. ## @operand string req The string in which to substitute variables. ## @return Returns 0 on success or 1 on possible recursion. ## @stderr Prints a warning on possible recursion. ## @pure yes This function has no side effects. ob_substvars() { local string="${1}" shift 1 || _ob_abort local depth= local lhs= local name= local rhs= local old_rhs= local value= # Logic inspired by that of dpkg's Dpkg::Substvars::substvars() # subroutine. depth=0 while :; do lhs="${string%%\$\{*}" if [ ${#lhs} -eq ${#string} ]; then # No "${" was found. break fi string="${string#*\$\{}" name="${string%%\}*}" rhs="${string#*\}}" if [ ${#rhs} -lt ${#old_rhs} ]; then # Reset the nesting counter if we've advanced the right # side of the matched space. depth=0 fi if [ ${depth} -ge ${_OB_SUBSTVARS_MAX_DEPTH} ]; then # Warn of possible recursion. _ob_error_msg 'substvar_deep_nesting' return 1 fi old_rhs="${rhs}" # Validate the variable name. case "${name}" in *[!A-Za-z0-9-]* | '') _ob_warn_msg 'substvar_invalid' "${name}" # Remove the variable expansion altogether. We can't # just leave the variable unexpanded, because the # presence of the "${" characters would cause this # parser to loop infinitely. string="${lhs}${rhs}" continue esac # Perform the substitution. name="$(tr 'a-z-' 'A-Z_' <<-EOF ${name} EOF )" value="$(eval "printf '%s' \"\${_OB_SUBSTVAR_${name}}\"")" string="${lhs}${value}${rhs}" depth=$((${depth} + 1)) done printf '%s\n' "${string}" return 0 }