/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "config.h" #include "conf.h" #include "conf-parse.h" #include #include #include /* * Simple recursive descent parser for an INI-like conf file grammar. The * grammar is, roughly: * * ::= * ::= * ::= | | "" * ::= "[" "]" * ::= "=" * * Where: * is any number of tabs or spaces. * is a # character followed by any length of text without an EOL. * is an alpha-numeric string consisting of: [A-Za-z0-9_-]. * is any length of text without an EOL or # character, or a double-quoted string (backslash escapes legal). * is a carriage return or line feed character. */ /** * The current section. Note that this means the parser is NOT threadsafe. */ char __guacd_current_section[GUACD_CONF_MAX_NAME_LENGTH + 1] = ""; char* guacd_conf_parse_error = NULL; char* guacd_conf_parse_error_location = NULL; /** * Reads through all whitespace at the beginning of the buffer, returning the * number of characters read. This is in the grammar above. As * the whitespace is zero or more whitespace characters, this function cannot * fail, but it may read zero chars overall. */ static int guacd_parse_whitespace(char* buffer, int length) { int chars_read = 0; /* Read through all whitespace */ while (chars_read < length) { /* Read character */ char c = *buffer; /* Stop at non-whitespace */ if (c != ' ' && c != '\t') break; chars_read++; buffer++; } return chars_read; } /** * Parses the name of a parameter, section, etc. A section/parameter name can * consist only of alphanumeric characters and underscores. The resulting name * will be stored in the name string, which must have at least 256 bytes * available. */ static int guacd_parse_name(char* buffer, int length, char* name) { char* name_start = buffer; int chars_read = 0; /* Read through all valid name chars */ while (chars_read < length) { /* Read character */ char c = *buffer; /* Stop at non-name characters */ if (!isalnum(c) && c != '_') break; chars_read++; buffer++; /* Ensure name does not exceed maximum length */ if (chars_read > GUACD_CONF_MAX_NAME_LENGTH) { guacd_conf_parse_error = "Names can be no more than 255 characters long"; guacd_conf_parse_error_location = buffer; return -1; } } /* Names must contain at least one character */ if (chars_read == 0) return 0; /* Copy name from buffer */ memcpy(name, name_start, chars_read); name[chars_read] = '\0'; return chars_read; } /** * Parses the value of a parameter. A value can consist of any character except * '#', whitespace, or EOL. The resulting value will be stored in the value * string, which must have at least 256 bytes available. */ static int guacd_parse_value(char* buffer, int length, char* value) { char* value_start = buffer; int chars_read = 0; /* Read through all valid value chars */ while (chars_read < length) { /* Read character */ char c = *buffer; /* Stop at invalid character */ if (c == '#' || c == '"' || c == '\r' || c == '\n' || c == ' ' || c == '\t') break; chars_read++; buffer++; /* Ensure value does not exceed maximum length */ if (chars_read > GUACD_CONF_MAX_VALUE_LENGTH) { guacd_conf_parse_error = "Values can be no more than 8191 characters long"; guacd_conf_parse_error_location = buffer; return -1; } } /* Values must contain at least one character */ if (chars_read == 0) { guacd_conf_parse_error = "Unquoted values must contain at least one character"; guacd_conf_parse_error_location = buffer; return -1; } /* Copy value from buffer */ memcpy(value, value_start, chars_read); value[chars_read] = '\0'; return chars_read; } /** * Parses the quoted value of a parameter. Quoted values may contain any * character except double quotes or backslashes, which must be * backslash-escaped. */ static int guacd_parse_quoted_value(char* buffer, int length, char* value) { int escaped = 0; /* Assert first character is '"' */ if (length == 0 || *buffer != '"') return 0; int chars_read = 1; buffer++; length--; /* Read until end of quoted value */ while (chars_read < length) { /* Read character */ char c = *buffer; /* Handle special characters if not escaped */ if (!escaped) { /* Stop at quote or invalid character */ if (c == '"' || c == '\r' || c == '\n') break; /* Backslash escaping */ else if (c == '\\') escaped = 1; else *(value++) = c; } /* Reset escape flag */ else { escaped = 0; *(value++) = c; } chars_read++; buffer++; /* Ensure value does not exceed maximum length */ if (chars_read > GUACD_CONF_MAX_VALUE_LENGTH) { guacd_conf_parse_error = "Values can be no more than 8191 characters long"; guacd_conf_parse_error_location = buffer; return -1; } } /* Assert value ends with '"' */ if (length == 0 || *buffer != '"') { guacd_conf_parse_error = "'\"' expected"; guacd_conf_parse_error_location = buffer; return -1; } chars_read++; /* Terminate read value */ *value = '\0'; return chars_read; } /** * Reads a parameter/value pair, separated by an '=' character. If the * parameter/value pair is invalid for any reason, a negative value is * returned. */ static int guacd_parse_parameter(guacd_param_callback* callback, char* buffer, int length, void* data) { char param_name[GUACD_CONF_MAX_NAME_LENGTH + 1]; char param_value[GUACD_CONF_MAX_VALUE_LENGTH + 1]; int retval; int chars_read = 0; char* param_start = buffer; retval = guacd_parse_name(buffer, length, param_name); if (retval < 0) return -1; /* If no name found, no parameter/value pair */ if (retval == 0) return 0; /* Validate presence of section header */ if (__guacd_current_section[0] == '\0') { guacd_conf_parse_error = "Parameters must have a corresponding section"; guacd_conf_parse_error_location = buffer; return -1; } chars_read += retval; buffer += retval; length -= retval; /* Optional whitespace before '=' */ retval = guacd_parse_whitespace(buffer, length); chars_read += retval; buffer += retval; length -= retval; /* Required '=' */ if (length == 0 || *buffer != '=') { guacd_conf_parse_error = "'=' expected"; guacd_conf_parse_error_location = buffer; return -1; } chars_read++; buffer++; length--; /* Optional whitespace before value */ retval = guacd_parse_whitespace(buffer, length); chars_read += retval; buffer += retval; length -= retval; /* Quoted parameter value */ retval = guacd_parse_quoted_value(buffer, length, param_value); if (retval < 0) return -1; /* Non-quoted parameter value (required if no quoted value given) */ if (retval == 0) retval = guacd_parse_value(buffer, length, param_value); if (retval < 0) return -1; chars_read += retval; /* Call callback, handling error code */ if (callback(__guacd_current_section, param_name, param_value, data)) { guacd_conf_parse_error_location = param_start; return -1; } return chars_read; } /** * Reads a section name from the beginning of the given buffer. This section * name must conform to the grammar definition. If the section name does not * match, a negative value is returned. */ static int guacd_parse_section(char* buffer, int length) { int retval; /* Assert first character is '[' */ if (length == 0 || *buffer != '[') return 0; int chars_read = 1; buffer++; length--; retval = guacd_parse_name(buffer, length, __guacd_current_section); if (retval < 0) return -1; /* If no name found, invalid section */ if (retval == 0) { guacd_conf_parse_error = "Section names must contain at least one character"; guacd_conf_parse_error_location = buffer; return -1; } chars_read += retval; buffer += retval; length -= retval; /* Name must end with ']' */ if (length == 0 || *buffer != ']') { guacd_conf_parse_error = "']' expected"; guacd_conf_parse_error_location = buffer; return -1; } chars_read++; return chars_read; } /** * Parses a declaration, which may be either a section name or a * parameter/value pair. The empty string is acceptable, as well, as a * "null declaration". */ static int guacd_parse_declaration(guacd_param_callback* callback, char* buffer, int length, void* data) { int retval; /* Look for section name */ retval = guacd_parse_section(buffer, length); if (retval != 0) return retval; /* Lacking a section name, read parameter/value pair */ retval = guacd_parse_parameter(callback, buffer, length, data); if (retval != 0) return retval; /* Null declaration (default) */ return 0; } /** * Parses a comment, which must start with a '#' character, and terminate with * an end-of-line character. If no EOL is found, or the first character is not * a '#', a negative value is returned. Otherwise, the number of characters * parsed is returned. If no comment is present, zero is returned. */ static int guacd_parse_comment(char* buffer, int length) { /* Need at least one character */ if (length == 0) return 0; /* Assert first character is '#' */ if (*(buffer++) != '#') return 0; int chars_read = 1; /* Advance to first non-space character */ while (chars_read < length) { /* Read character */ char c = *buffer; /* End of comment found at end of line */ if (c == '\n' || c == '\r') return chars_read; chars_read++; buffer++; } /* No end of line in comment */ guacd_conf_parse_error = "expected end-of-line"; guacd_conf_parse_error_location = buffer; return -1; } /** * Parses the end of a line, which may contain a comment. If a parse error * occurs, a negative value is returned. Otherwise, the number of characters * parsed is returned. */ static int guacd_parse_line_end(char* buffer, int length) { int chars_read = 0; int retval; /* Initial optional whitespace */ retval = guacd_parse_whitespace(buffer, length); chars_read += retval; buffer += retval; length -= retval; /* Optional comment */ retval = guacd_parse_comment(buffer, length); if (retval < 0) return -1; chars_read += retval; buffer += retval; length -= retval; /* Assert EOL */ if (length == 0 || (*buffer != '\r' && *buffer != '\n')) { guacd_conf_parse_error = "expected end-of-line"; guacd_conf_parse_error_location = buffer; return -1; } chars_read++; /* Line is valid */ return chars_read; } /** * Parses an entire line - declaration, comment, and all. If a parse error * occurs, a negative value is returned. Otherwise, the number of characters * parsed is returned. */ static int guacd_parse_line(guacd_param_callback* callback, char* buffer, int length, void* data) { int chars_read = 0; int retval; /* Initial optional whitespace */ retval = guacd_parse_whitespace(buffer, length); chars_read += retval; buffer += retval; length -= retval; /* Declaration (which may be the empty string) */ retval = guacd_parse_declaration(callback, buffer, length, data); if (retval < 0) return retval; chars_read += retval; buffer += retval; length -= retval; /* End of line */ retval = guacd_parse_line_end(buffer, length); if (retval < 0) return retval; chars_read += retval; return chars_read; } int guacd_parse_conf(guacd_param_callback* callback, char* buffer, int length, void* data) { /* Empty buffers are valid */ if (length == 0) return 0; return guacd_parse_line(callback, buffer, length, data); } int guacd_parse_log_level(const char* name) { /* Translate log level name */ if (strcmp(name, "info") == 0) return GUAC_LOG_INFO; if (strcmp(name, "error") == 0) return GUAC_LOG_ERROR; if (strcmp(name, "warning") == 0) return GUAC_LOG_WARNING; if (strcmp(name, "debug") == 0) return GUAC_LOG_DEBUG; if (strcmp(name, "trace") == 0) return GUAC_LOG_TRACE; /* No such log level */ return -1; }