# 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_SUBSTVAR_TRIM_SED=' 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. }; ' _ob_parse_control_error() { local file="${1}" local line_nr="${2}" local 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} " shift 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 source package substitution variable ## @details \fBob_set_source_substvar\fP() sets a source package 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_source_substvar() { local name="${1}" local value="${2}" shift 2 || _ob_abort # Validate variable name and convert to lower case. case "${name}" in *[!A-Za-z0-9-]* | '') return 1 esac name="$(printf '%s' "${name}" | tr 'A-Z-' 'a-z_')" # Trim leading and trailing whitespace from value. value="$(printf '%s' "${value}" | sed -n "${_OB_SUBSTVAR_TRIM_SED}")" eval "_ob_substvar_src_${name}=\"\${value}\"" return 0 } ## @brief Set a binary package substitution variable ## @details \fBob_set_source_substvar\fP() sets a binary package 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. ## @operand package req The package for which to set 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_binary_substvar() { local name="${1}" local value="${2}" local package="${3}" shift 3 || _ob_abort # Validate variable name and convert to lower case. case "${name}" in *[!A-Za-z0-9-]* | '') return 1 esac name="$(printf '%s' "${name}" | tr 'A-Z-' 'a-z_')" # Trim leading and trailing whitespace from value. value="$(printf '%s' "${value}" | sed -n "${_OB_SUBSTVAR_TRIM_SED}")" # Convert package name to clean form. package="$(printf '%s' "${package}" | tr 'A-Z' 'a-z' | \ tr -C 'a-z0-9' '_')" # TODO: Maybe disallow variable names beginning with hyphens, and/or # otherwise make this safer. eval "_ob_substvar_bin_${package}__${name}=\"\${value}\"" return 0 } ## @brief Substitute variables in text ## @details \fBob_substvars\fP() substitutes variables previously set with ## \fBob_set_source_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. ## @operand package opt The binary package for 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 check_bin= local package= local depth= local lhs= local name= local rhs= local old_rhs= local name_tr= local value= check_bin=false if [ ${#} -eq 1 ]; then package="${1}" shift 1 check_bin=true fi # 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="$(printf '%s' "${name}" | tr 'A-Z-' 'a-z_')" package="$(printf '%s' "${package}" | tr 'A-Z' 'a-z' | \ tr -C 'a-z0-9' '_')" if eval "[ x\"\${_ob_substvar_src_${name_tr}:+set}\" = x'set' ]" then name_tr="_ob_substvar_src_${name_tr}" elif ${check_bin} && eval "[ x\"\${_ob_substvar_bin_$(: \ )${package}__${name_tr}:+set}\" = x'set' ]"; then name_tr="_ob_substvar_bin_${package}__${name_tr}" else _ob_warn_msg 'substvar_unknown' "${name}" string="${lhs}${rhs}" continue fi value="$(eval "printf '%s' \"\${${name_tr}}\"")" string="${lhs}${value}${rhs}" depth=$((${depth} + 1)) done printf '%s\n' "${string}" return 0 }