conf-parse.c (14228B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "conf.h" 23#include "conf-parse.h" 24 25#include <guacamole/client.h> 26 27#include <ctype.h> 28#include <string.h> 29 30/* 31 * Simple recursive descent parser for an INI-like conf file grammar. The 32 * grammar is, roughly: 33 * 34 * <line> ::= <opt-whitespace> <declaration> <line-end> 35 * <line-end> ::= <opt-whitespace> <opt-comment> <EOL> 36 * <declaration> ::= <section-name> | <parameter-value> | "" 37 * <section-name> ::= "[" <name> "]" 38 * <parameter-value> ::= <name> <opt-whitespace> "=" <opt-whitespace> <value> 39 * 40 * Where: 41 * <opt-whitespace> is any number of tabs or spaces. 42 * <opt-comment> is a # character followed by any length of text without an EOL. 43 * <name> is an alpha-numeric string consisting of: [A-Za-z0-9_-]. 44 * <value> is any length of text without an EOL or # character, or a double-quoted string (backslash escapes legal). 45 * <EOL> is a carriage return or line feed character. 46 */ 47 48/** 49 * The current section. Note that this means the parser is NOT threadsafe. 50 */ 51char __guacd_current_section[GUACD_CONF_MAX_NAME_LENGTH + 1] = ""; 52 53char* guacd_conf_parse_error = NULL; 54 55char* guacd_conf_parse_error_location = NULL; 56 57/** 58 * Reads through all whitespace at the beginning of the buffer, returning the 59 * number of characters read. This is <opt-whitespace> in the grammar above. As 60 * the whitespace is zero or more whitespace characters, this function cannot 61 * fail, but it may read zero chars overall. 62 */ 63static int guacd_parse_whitespace(char* buffer, int length) { 64 65 int chars_read = 0; 66 67 /* Read through all whitespace */ 68 while (chars_read < length) { 69 70 /* Read character */ 71 char c = *buffer; 72 73 /* Stop at non-whitespace */ 74 if (c != ' ' && c != '\t') 75 break; 76 77 chars_read++; 78 buffer++; 79 80 } 81 82 return chars_read; 83 84} 85 86/** 87 * Parses the name of a parameter, section, etc. A section/parameter name can 88 * consist only of alphanumeric characters and underscores. The resulting name 89 * will be stored in the name string, which must have at least 256 bytes 90 * available. 91 */ 92static int guacd_parse_name(char* buffer, int length, char* name) { 93 94 char* name_start = buffer; 95 int chars_read = 0; 96 97 /* Read through all valid name chars */ 98 while (chars_read < length) { 99 100 /* Read character */ 101 char c = *buffer; 102 103 /* Stop at non-name characters */ 104 if (!isalnum(c) && c != '_') 105 break; 106 107 chars_read++; 108 buffer++; 109 110 /* Ensure name does not exceed maximum length */ 111 if (chars_read > GUACD_CONF_MAX_NAME_LENGTH) { 112 guacd_conf_parse_error = "Names can be no more than 255 characters long"; 113 guacd_conf_parse_error_location = buffer; 114 return -1; 115 } 116 117 } 118 119 /* Names must contain at least one character */ 120 if (chars_read == 0) 121 return 0; 122 123 /* Copy name from buffer */ 124 memcpy(name, name_start, chars_read); 125 name[chars_read] = '\0'; 126 127 return chars_read; 128 129} 130 131/** 132 * Parses the value of a parameter. A value can consist of any character except 133 * '#', whitespace, or EOL. The resulting value will be stored in the value 134 * string, which must have at least 256 bytes available. 135 */ 136static int guacd_parse_value(char* buffer, int length, char* value) { 137 138 char* value_start = buffer; 139 int chars_read = 0; 140 141 /* Read through all valid value chars */ 142 while (chars_read < length) { 143 144 /* Read character */ 145 char c = *buffer; 146 147 /* Stop at invalid character */ 148 if (c == '#' || c == '"' || c == '\r' || c == '\n' || c == ' ' || c == '\t') 149 break; 150 151 chars_read++; 152 buffer++; 153 154 /* Ensure value does not exceed maximum length */ 155 if (chars_read > GUACD_CONF_MAX_VALUE_LENGTH) { 156 guacd_conf_parse_error = "Values can be no more than 8191 characters long"; 157 guacd_conf_parse_error_location = buffer; 158 return -1; 159 } 160 161 } 162 163 /* Values must contain at least one character */ 164 if (chars_read == 0) { 165 guacd_conf_parse_error = "Unquoted values must contain at least one character"; 166 guacd_conf_parse_error_location = buffer; 167 return -1; 168 } 169 170 /* Copy value from buffer */ 171 memcpy(value, value_start, chars_read); 172 value[chars_read] = '\0'; 173 174 return chars_read; 175 176} 177 178/** 179 * Parses the quoted value of a parameter. Quoted values may contain any 180 * character except double quotes or backslashes, which must be 181 * backslash-escaped. 182 */ 183static int guacd_parse_quoted_value(char* buffer, int length, char* value) { 184 185 int escaped = 0; 186 187 /* Assert first character is '"' */ 188 if (length == 0 || *buffer != '"') 189 return 0; 190 191 int chars_read = 1; 192 buffer++; 193 length--; 194 195 /* Read until end of quoted value */ 196 while (chars_read < length) { 197 198 /* Read character */ 199 char c = *buffer; 200 201 /* Handle special characters if not escaped */ 202 if (!escaped) { 203 204 /* Stop at quote or invalid character */ 205 if (c == '"' || c == '\r' || c == '\n') 206 break; 207 208 /* Backslash escaping */ 209 else if (c == '\\') 210 escaped = 1; 211 212 else 213 *(value++) = c; 214 215 } 216 217 /* Reset escape flag */ 218 else { 219 escaped = 0; 220 *(value++) = c; 221 } 222 223 chars_read++; 224 buffer++; 225 226 /* Ensure value does not exceed maximum length */ 227 if (chars_read > GUACD_CONF_MAX_VALUE_LENGTH) { 228 guacd_conf_parse_error = "Values can be no more than 8191 characters long"; 229 guacd_conf_parse_error_location = buffer; 230 return -1; 231 } 232 233 } 234 235 /* Assert value ends with '"' */ 236 if (length == 0 || *buffer != '"') { 237 guacd_conf_parse_error = "'\"' expected"; 238 guacd_conf_parse_error_location = buffer; 239 return -1; 240 } 241 242 chars_read++; 243 244 /* Terminate read value */ 245 *value = '\0'; 246 247 return chars_read; 248 249} 250 251/** 252 * Reads a parameter/value pair, separated by an '=' character. If the 253 * parameter/value pair is invalid for any reason, a negative value is 254 * returned. 255 */ 256static int guacd_parse_parameter(guacd_param_callback* callback, char* buffer, int length, void* data) { 257 258 char param_name[GUACD_CONF_MAX_NAME_LENGTH + 1]; 259 char param_value[GUACD_CONF_MAX_VALUE_LENGTH + 1]; 260 261 int retval; 262 int chars_read = 0; 263 264 char* param_start = buffer; 265 266 retval = guacd_parse_name(buffer, length, param_name); 267 if (retval < 0) 268 return -1; 269 270 /* If no name found, no parameter/value pair */ 271 if (retval == 0) 272 return 0; 273 274 /* Validate presence of section header */ 275 if (__guacd_current_section[0] == '\0') { 276 guacd_conf_parse_error = "Parameters must have a corresponding section"; 277 guacd_conf_parse_error_location = buffer; 278 return -1; 279 } 280 281 chars_read += retval; 282 buffer += retval; 283 length -= retval; 284 285 /* Optional whitespace before '=' */ 286 retval = guacd_parse_whitespace(buffer, length); 287 chars_read += retval; 288 buffer += retval; 289 length -= retval; 290 291 /* Required '=' */ 292 if (length == 0 || *buffer != '=') { 293 guacd_conf_parse_error = "'=' expected"; 294 guacd_conf_parse_error_location = buffer; 295 return -1; 296 } 297 298 chars_read++; 299 buffer++; 300 length--; 301 302 /* Optional whitespace before value */ 303 retval = guacd_parse_whitespace(buffer, length); 304 chars_read += retval; 305 buffer += retval; 306 length -= retval; 307 308 /* Quoted parameter value */ 309 retval = guacd_parse_quoted_value(buffer, length, param_value); 310 if (retval < 0) 311 return -1; 312 313 /* Non-quoted parameter value (required if no quoted value given) */ 314 if (retval == 0) retval = guacd_parse_value(buffer, length, param_value); 315 if (retval < 0) 316 return -1; 317 318 chars_read += retval; 319 320 /* Call callback, handling error code */ 321 if (callback(__guacd_current_section, param_name, param_value, data)) { 322 guacd_conf_parse_error_location = param_start; 323 return -1; 324 } 325 326 return chars_read; 327 328} 329 330/** 331 * Reads a section name from the beginning of the given buffer. This section 332 * name must conform to the grammar definition. If the section name does not 333 * match, a negative value is returned. 334 */ 335static int guacd_parse_section(char* buffer, int length) { 336 337 int retval; 338 339 /* Assert first character is '[' */ 340 if (length == 0 || *buffer != '[') 341 return 0; 342 343 int chars_read = 1; 344 buffer++; 345 length--; 346 347 retval = guacd_parse_name(buffer, length, __guacd_current_section); 348 if (retval < 0) 349 return -1; 350 351 /* If no name found, invalid section */ 352 if (retval == 0) { 353 guacd_conf_parse_error = "Section names must contain at least one character"; 354 guacd_conf_parse_error_location = buffer; 355 return -1; 356 } 357 358 chars_read += retval; 359 buffer += retval; 360 length -= retval; 361 362 /* Name must end with ']' */ 363 if (length == 0 || *buffer != ']') { 364 guacd_conf_parse_error = "']' expected"; 365 guacd_conf_parse_error_location = buffer; 366 return -1; 367 } 368 369 chars_read++; 370 371 return chars_read; 372 373} 374 375/** 376 * Parses a declaration, which may be either a section name or a 377 * parameter/value pair. The empty string is acceptable, as well, as a 378 * "null declaration". 379 */ 380static int guacd_parse_declaration(guacd_param_callback* callback, char* buffer, int length, void* data) { 381 382 int retval; 383 384 /* Look for section name */ 385 retval = guacd_parse_section(buffer, length); 386 if (retval != 0) 387 return retval; 388 389 /* Lacking a section name, read parameter/value pair */ 390 retval = guacd_parse_parameter(callback, buffer, length, data); 391 if (retval != 0) 392 return retval; 393 394 /* Null declaration (default) */ 395 return 0; 396 397} 398 399/** 400 * Parses a comment, which must start with a '#' character, and terminate with 401 * an end-of-line character. If no EOL is found, or the first character is not 402 * a '#', a negative value is returned. Otherwise, the number of characters 403 * parsed is returned. If no comment is present, zero is returned. 404 */ 405static int guacd_parse_comment(char* buffer, int length) { 406 407 /* Need at least one character */ 408 if (length == 0) 409 return 0; 410 411 /* Assert first character is '#' */ 412 if (*(buffer++) != '#') 413 return 0; 414 415 int chars_read = 1; 416 417 /* Advance to first non-space character */ 418 while (chars_read < length) { 419 420 /* Read character */ 421 char c = *buffer; 422 423 /* End of comment found at end of line */ 424 if (c == '\n' || c == '\r') 425 return chars_read; 426 427 chars_read++; 428 buffer++; 429 430 } 431 432 /* No end of line in comment */ 433 guacd_conf_parse_error = "expected end-of-line"; 434 guacd_conf_parse_error_location = buffer; 435 return -1; 436 437} 438 439/** 440 * Parses the end of a line, which may contain a comment. If a parse error 441 * occurs, a negative value is returned. Otherwise, the number of characters 442 * parsed is returned. 443 */ 444static int guacd_parse_line_end(char* buffer, int length) { 445 446 int chars_read = 0; 447 int retval; 448 449 /* Initial optional whitespace */ 450 retval = guacd_parse_whitespace(buffer, length); 451 chars_read += retval; 452 buffer += retval; 453 length -= retval; 454 455 /* Optional comment */ 456 retval = guacd_parse_comment(buffer, length); 457 if (retval < 0) 458 return -1; 459 460 chars_read += retval; 461 buffer += retval; 462 length -= retval; 463 464 /* Assert EOL */ 465 if (length == 0 || (*buffer != '\r' && *buffer != '\n')) { 466 guacd_conf_parse_error = "expected end-of-line"; 467 guacd_conf_parse_error_location = buffer; 468 return -1; 469 } 470 471 chars_read++; 472 473 /* Line is valid */ 474 return chars_read; 475 476} 477 478/** 479 * Parses an entire line - declaration, comment, and all. If a parse error 480 * occurs, a negative value is returned. Otherwise, the number of characters 481 * parsed is returned. 482 */ 483static int guacd_parse_line(guacd_param_callback* callback, char* buffer, int length, void* data) { 484 485 int chars_read = 0; 486 int retval; 487 488 /* Initial optional whitespace */ 489 retval = guacd_parse_whitespace(buffer, length); 490 chars_read += retval; 491 buffer += retval; 492 length -= retval; 493 494 /* Declaration (which may be the empty string) */ 495 retval = guacd_parse_declaration(callback, buffer, length, data); 496 if (retval < 0) 497 return retval; 498 499 chars_read += retval; 500 buffer += retval; 501 length -= retval; 502 503 /* End of line */ 504 retval = guacd_parse_line_end(buffer, length); 505 if (retval < 0) 506 return retval; 507 508 chars_read += retval; 509 510 return chars_read; 511 512} 513 514int guacd_parse_conf(guacd_param_callback* callback, char* buffer, int length, void* data) { 515 516 /* Empty buffers are valid */ 517 if (length == 0) 518 return 0; 519 520 return guacd_parse_line(callback, buffer, length, data); 521 522} 523 524int guacd_parse_log_level(const char* name) { 525 526 /* Translate log level name */ 527 if (strcmp(name, "info") == 0) return GUAC_LOG_INFO; 528 if (strcmp(name, "error") == 0) return GUAC_LOG_ERROR; 529 if (strcmp(name, "warning") == 0) return GUAC_LOG_WARNING; 530 if (strcmp(name, "debug") == 0) return GUAC_LOG_DEBUG; 531 if (strcmp(name, "trace") == 0) return GUAC_LOG_TRACE; 532 533 /* No such log level */ 534 return -1; 535 536} 537