-/* Copyright (C) 2003 Aleksey Krivoshey <krivoshey@users.sourceforge.net>
-*
-* This program 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 2 of the License, or
-* (at your option) any later version.
-*
-* This program 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 this program; if not, write to the Free Software
-* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-#include <libgen.h> // dirname
-#include <glob.h> // glob
-#include <string>
-
-#include "dotconfpp.h"
-
-DOTCONFDocumentNode::DOTCONFDocumentNode():previousNode(NULL), nextNode(NULL), parentNode(NULL), childNode(NULL),
- values(NULL), valuesCount(0),
- name(NULL), lineNum(0), fileName(NULL), closed(true)
-{
-}
-
-DOTCONFDocumentNode::~DOTCONFDocumentNode()
-{
- free(name);
- if(values != NULL){
- for(int i = 0 ; i < valuesCount; i++){
- free(values[i]);
- }
- free(values);
- }
-}
-
-void DOTCONFDocumentNode::pushValue(char * _value)
-{
- valuesCount++;
- values = (char**)realloc(values, valuesCount*sizeof(char*));
- values[valuesCount-1] = strdup(_value);
-}
-
-const char* DOTCONFDocumentNode::getValue(int index) const
-{
- if(index >= valuesCount){
- return NULL;
- }
- return values[index];
-}
-
-DOTCONFDocument::DOTCONFDocument(DOTCONFDocument::CaseSensitive caseSensitivity):
- mempool(NULL),
- curParent(NULL), curPrev(NULL), errorCallback(NULL), errorCallbackData(NULL),
- curLine(0), file(NULL), fileName(NULL)
-{
- if(caseSensitivity == CASESENSITIVE){
- cmp_func = strcmp;
- } else {
- cmp_func = strcasecmp;
- }
-
- mempool = new AsyncDNSMemPool(1024);
- mempool->initialize();
-}
-
-DOTCONFDocument::~DOTCONFDocument()
-{
- for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i != nodeTree.end(); i++){
- delete(*i);
- }
- for(std::list<char*>::iterator i = requiredOptions.begin(); i != requiredOptions.end(); i++){
- free(*i);
- }
- for(std::list<char*>::iterator i = processedFiles.begin(); i != processedFiles.end(); i++){
- free(*i);
- }
- free(fileName);
- delete mempool;
-}
-
-int DOTCONFDocument::cleanupLine(char * line)
-{
- char * start = line;
- char * bg = line;
- bool multiline = false;
- bool concat = false;
- char * word = NULL;
-
- if(!words.empty() && quoted)
- concat = true;
-
- while(*line){
- if((*line == '#' || *line == ';') && !quoted){
- *bg = 0;
- if(strlen(start)){
- //printf("2start='%s'\n", start);
- if(concat){
- word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
- strcpy(word, words.back());
- strcat(word, start);
- words.pop_back();
- concat = false;
- } else {
- word = mempool->strdup(start);
- }
- words.push_back(word);
- }
- break;
- }
- if(*line == '=' && !quoted){ // 'parameter = value' is the same as 'parameter value' but do not replace with ' ' when used in quoted value
- *line = ' ';continue;
- }
- if(*line == '\\' && (*(line+1) == '"' || *(line+1) == '\'')){
- *bg++ = *(line+1);
- line+=2; continue;
- }
- if(*line == '\\' && *(line+1) == 'n'){
- *bg++ = '\n';
- line+=2; continue;
- }
- if(*line == '\\' && *(line+1) == 'r'){
- *bg++ = '\r';
- line+=2; continue;
- }
- if(*line == '\\' && (*(line+1) == '\n' || *(line+1) == '\r')){ //multiline
- *bg = 0;
- if(strlen(start)){
- //printf("3start='%s'\n", start);
- if(concat){
- word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
- strcpy(word, words.back());
- strcat(word, start);
- words.pop_back();
- concat = false;
- } else {
- word = mempool->strdup(start);
- }
- words.push_back(word);
- }
- multiline = true;
- break;
- }
- if(*line == '"' || *line == '\''){ //need to handle quotes because of spaces or = that may be between
- quoted = !quoted;
- line++; continue;
- }
- if(isspace(*line) && !quoted){
- *bg++ = 0;
- if(strlen(start)){
- //printf("start='%s'\n", start);
- if(concat){
- word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
- strcpy(word, words.back());
- strcat(word, start);
- words.pop_back();
- concat = false;
- } else {
- word = mempool->strdup(start);
- }
- words.push_back(word);
- }
- start = bg;
- while(isspace(*++line)) {};
- continue;
- }
- *bg++ = *line++;
- }
-
- if(quoted && !multiline){
- error(curLine, fileName, "unterminated quote");
- return -1;
- }
-
- return multiline?1:0;
-}
-
-int DOTCONFDocument::parseLine()
-{
- char * word = NULL;
- char * nodeName = NULL;
- char * nodeValue = NULL;
- DOTCONFDocumentNode * tagNode = NULL;
- bool newNode = false;
-
- for(std::list<char*>::iterator i = words.begin(); i != words.end(); i++) {
- word = *i;
-
- if(*word == '<'){
- newNode = true;
- }
-
- if(newNode){
- nodeValue = NULL;
- nodeName = NULL;
- newNode = false;
- }
-
- size_t wordLen = strlen(word);
- if(word[wordLen-1] == '>'){
- word[wordLen-1] = 0;
- newNode = true;
- }
-
- if(nodeName == NULL){
- nodeName = word;
- bool closed = true; //if this not <> node then it is closed by default
- if(*nodeName == '<'){
- if(*(nodeName+1) != '/'){ //opening tag
- nodeName++;
- closed = false;
- } else { //closing tag
- nodeName+=2;
- std::list<DOTCONFDocumentNode*>::reverse_iterator i=nodeTree.rbegin();
- for(; i!=nodeTree.rend(); i++){
- if(!cmp_func(nodeName, (*i)->name) && !(*i)->closed){
- (*i)->closed = true;
- curParent = (*i)->parentNode;
- curPrev = *i;
- break;
- }
- }
- if(i==nodeTree.rend()){
- error(curLine, fileName, "not matched closing tag </%s>", nodeName);
- return -1;
- }
- continue;
- }
- }
- tagNode = new DOTCONFDocumentNode;
- tagNode->name = strdup(nodeName);
- tagNode->document = this;
- tagNode->fileName = processedFiles.back();
- tagNode->lineNum = curLine;
- tagNode->closed = closed;
- if(!nodeTree.empty()){
- DOTCONFDocumentNode * prev = nodeTree.back();
- if(prev->closed){
-
- curPrev->nextNode = tagNode;
- tagNode->previousNode = curPrev;
- tagNode->parentNode = curParent;
-
- } else {
- prev->childNode = tagNode;
- tagNode->parentNode = prev;
- curParent = prev;
- }
- }
- nodeTree.push_back(tagNode);
- curPrev = tagNode;
- } else {
- nodeValue = word;
- tagNode->pushValue(nodeValue);
- }
- }
-
- return 0;
-}
-int DOTCONFDocument::parseFile(DOTCONFDocumentNode * _parent)
-{
- char str[512];
- int ret = 0;
- curLine = 0;
- curParent = _parent;
-
- quoted = false;
- size_t slen = 0;
-
- while(fgets(str, 511, file)){
- curLine++;
- slen = strlen(str);
- if( slen >= 510 ){
- error(curLine, fileName, "warning: line too long");
- }
- if(str[slen-1] != '\n'){
- str[slen] = '\n';
- str[slen+1] = 0;
- }
- if((ret = cleanupLine(str)) == -1){
- break;
- }
- if(ret == 0){
- if(!words.empty()){
- ret = parseLine();
- mempool->free();
- words.clear();
- if(ret == -1){
- break;
- }
- }
- }
- }
-
- return ret;
-}
-
-int DOTCONFDocument::checkConfig(const std::list<DOTCONFDocumentNode*>::iterator & from)
-{
- int ret = 0;
-
- DOTCONFDocumentNode * tagNode = NULL;
- int vi = 0;
- for(std::list<DOTCONFDocumentNode*>::iterator i = from; i != nodeTree.end(); i++){
- tagNode = *i;
- if(!tagNode->closed){
- error(tagNode->lineNum, tagNode->fileName, "unclosed tag %s", tagNode->name);
- ret = -1;
- break;
- }
- vi = 0;
- while( vi < tagNode->valuesCount ){
- //if((tagNode->values[vi])[0] == '$' && (tagNode->values[vi])[1] == '{' && strchr(tagNode->values[vi], '}') ){
- if(strstr(tagNode->values[vi], "${") && strchr(tagNode->values[vi], '}') ){
- ret = macroSubstitute(tagNode, vi );
- mempool->free();
- if(ret == -1){
- break;
- }
- }
- vi++;
- }
- if(ret == -1){
- break;
- }
- }
-
- return ret;
-}
-
-int DOTCONFDocument::setContent(const char * _fileName)
-{
- int ret = 0;
- char realpathBuf[PATH_MAX];
-
- if(realpath(_fileName, realpathBuf) == NULL){
- error(0, NULL, "realpath(%s) failed: %s", _fileName, strerror(errno));
- return -1;
- }
-
- fileName = strdup(realpathBuf);
-
- char * forPathName = strdup(realpathBuf);
-
- if (forPathName == NULL) {
- error(0, NULL, "Not enought memory to duplicate realpath");
- return -1;
- }
-
- char * _pathName = dirname(forPathName);
-
- std::string pathName(_pathName);
-
- free(forPathName); // From strdup
-
- processedFiles.push_back(strdup(realpathBuf));
-
- if(( file = fopen(fileName, "r")) == NULL){
- error(0, NULL, "failed to open file '%s': %s", fileName, strerror(errno));
- return -1;
- }
-
- ret = parseFile();
-
- (void) fclose(file);
-
- if(!ret){
-
- if( (ret = checkConfig(nodeTree.begin())) == -1){
- return -1;
- }
-
- std::list<DOTCONFDocumentNode*>::iterator from;
- DOTCONFDocumentNode * tagNode = NULL;
- int vi = 0;
- for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); i++){
- tagNode = *i;
- if(!cmp_func("IncludeFile", tagNode->name)){
- vi = 0;
- while( vi < tagNode->valuesCount ){
- glob_t globBuf;
- std::string nodeFilePath;
- if (*tagNode->values[vi] != '/') {
- // Relative path
- nodeFilePath = pathName + "/" + tagNode->values[vi];
- } else {
- // Absolute path
- nodeFilePath = tagNode->values[vi];
- }
- int res = glob(nodeFilePath.c_str(), 0, NULL, &globBuf);
- if (res) {
- switch (res) {
- case GLOB_NOSPACE:
- error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': no free space", nodeFilePath.c_str());
- return -1;
-#ifndef FREE_BSD
- case GLOB_ABORTED:
- // printf("Read error\n");
- // Ignore that error
- break;
- case GLOB_NOMATCH:
- // printf("No match\n");
- // Ignore that error
- break;
-#endif
- default:
- error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': unknown error", nodeFilePath.c_str());
- return -1;
- }
- }
- if (!res) {
- for (size_t i = 0; i < globBuf.gl_pathc; ++i) {
- std::string nodeFilePath(globBuf.gl_pathv[i]);
- if(access(nodeFilePath.c_str(), R_OK) == -1){
- error(tagNode->lineNum, tagNode->fileName, "%s: %s", nodeFilePath.c_str(), strerror(errno));
- continue;
- }
- if(realpath(nodeFilePath.c_str(), realpathBuf) == NULL){
- error(tagNode->lineNum, tagNode->fileName, "realpath(%s) failed: %s", nodeFilePath.c_str(), strerror(errno));
- continue;
- }
-
- bool processed = false;
- for(std::list<char*>::const_iterator itInode = processedFiles.begin(); itInode != processedFiles.end(); itInode++){
- if(!strcmp(*itInode, realpathBuf)){
- processed = true;
- break;
- }
- }
- if(processed){
- break;
- }
-
- processedFiles.push_back(strdup(realpathBuf));
-
- file = fopen(nodeFilePath.c_str(), "r");
- if(file == NULL){
- error(tagNode->lineNum, fileName, "failed to open file '%s': %s", nodeFilePath.c_str(), strerror(errno));
- continue;
- }
- //free(fileName);
- fileName = strdup(realpathBuf);
- from = nodeTree.end(); from--;
-
- if(tagNode->parentNode){
- DOTCONFDocumentNode * nd = tagNode->parentNode->childNode;
- while(nd){
- if(!nd->nextNode)
- break;
- nd = nd->nextNode;
- }
-
- curPrev = nd;
- }
- ret = parseFile(tagNode->parentNode);
-
- //ret = parseFile(tagNode->parentNode);
- (void) fclose(file);
- if(ret == -1)
- continue;
- if(checkConfig(++from) == -1){
- continue;
- }
- }
- }
- globfree(&globBuf);
- vi++;
- }
- }
- }
- /*
- if( (ret = checkConfig(nodeTree.begin())) == -1){
- return -1;
- }
- */
-
- if(!requiredOptions.empty())
- ret = checkRequiredOptions();
- }
-
- return ret;
-}
-
-int DOTCONFDocument::checkRequiredOptions()
-{
- for(std::list<char*>::const_iterator ci = requiredOptions.begin(); ci != requiredOptions.end(); ci++){
- bool matched = false;
- for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); i++){
- if(!cmp_func((*i)->name, *ci)){
- matched = true;
- break;
- }
- }
- if(!matched){
- error(0, NULL, "required option '%s' not specified", *ci);
- return -1;
- }
- }
- return 0;
-}
-
-void DOTCONFDocument::error(int lineNum, const char * fileName, const char * fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
-
- size_t len = (lineNum!=0?strlen(fileName):0) + strlen(fmt) + 50;
- char * buf = (char*)mempool->alloc(len);
-
- if(lineNum)
- (void) snprintf(buf, len, "DOTCONF++: file '%s', line %d: %s\n", fileName, lineNum, fmt);
- else
- (void) snprintf(buf, len, "DOTCONF++: %s\n", fmt);
-
- if (errorCallback) {
- errorCallback(errorCallbackData, buf);
- } else {
- (void) vfprintf(stderr, buf, args);
- }
-
- va_end(args);
-}
-
-char * DOTCONFDocument::getSubstitution(char * macro, int lineNum)
-{
- char * buf = NULL;
- char * variable = macro+2;
-
- char * endBr = strchr(macro, '}');
-
- if(!endBr){
- error(lineNum, fileName, "unterminated '{'");
- return NULL;
- }
- *endBr = 0;
-
- char * defaultValue = strchr(variable, ':');
-
- if(defaultValue){
- *defaultValue++ = 0;
- if(*defaultValue != '-'){
- error(lineNum, fileName, "incorrect macro substitution syntax");
- return NULL;
- }
- defaultValue++;
- if(*defaultValue == '"' || *defaultValue == '\''){
- defaultValue++;
- defaultValue[strlen(defaultValue)-1] = 0;
- }
- } else {
- defaultValue = NULL;
- }
-
- char * subs = getenv(variable);
- if( subs ){
- buf = mempool->strdup(subs);
- } else {
- std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin();
- DOTCONFDocumentNode * tagNode = NULL;
- for(; i!=nodeTree.end(); i++){
- tagNode = *i;
- if(!cmp_func(tagNode->name, variable)){
- if(tagNode->valuesCount != 0){
- buf = mempool->strdup(tagNode->values[0]);
- break;
- }
- }
- }
- if( i == nodeTree.end() ){
- if( defaultValue ){
- buf = mempool->strdup(defaultValue);
- } else {
- error(lineNum, fileName, "substitution not found and default value not given");
- return NULL;
- }
- }
- }
- return buf;
-}
-
-int DOTCONFDocument::macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex)
-{
- int ret = 0;
- char * macro = tagNode->values[valueIndex];
- size_t valueLen = strlen(tagNode->values[valueIndex])+1;
- char * value = (char*)mempool->alloc(valueLen);
- char * v = value;
- char * subs = NULL;
-
- while(*macro){
- if(*macro == '$' && *(macro+1) == '{'){
- char * m = strchr(macro, '}');
- subs = getSubstitution(macro, tagNode->lineNum);
- if(subs == NULL){
- ret = -1;
- break;
- }
- macro = m + 1;
- *v = 0;
- v = (char*)mempool->alloc(strlen(value)+strlen(subs)+valueLen);
- strcpy(v, value);
- value = strcat(v, subs);
- v = value + strlen(value);
- continue;
- }
- *v++ = *macro++;
- }
- *v = 0;
-
- free(tagNode->values[valueIndex]);
- tagNode->values[valueIndex] = strdup(value);
- return ret;
-}
-
-const DOTCONFDocumentNode * DOTCONFDocument::getFirstNode() const
-{
- if ( !nodeTree.empty() ) {
- return *nodeTree.begin();
- } else {
- return NULL;
- }
-}
-
-const DOTCONFDocumentNode * DOTCONFDocument::findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode, const DOTCONFDocumentNode * startNode) const
-{
- //printf("nodeName=%s, cont=%s, start=%s\n", nodeName, containingNode!=NULL?containingNode->name:"NULL", startNode!=NULL?startNode->name:"NULL");
-
- std::list<DOTCONFDocumentNode*>::const_iterator i = nodeTree.begin();
-
- if(startNode == NULL)
- startNode = parentNode;
-
- if(startNode != NULL){
- while( i != nodeTree.end() && (*i) != startNode ){
- i++;
- }
- if( i != nodeTree.end() ) i++;
- }
-
- for(; i!=nodeTree.end(); i++){
- //if(parentNode != NULL && (*i)->parentNode != parentNode){
- if((*i)->parentNode != parentNode){
- continue;
- }
- if(!cmp_func(nodeName, (*i)->name)){
- return *i;
- }
- }
- return NULL;
-}
-
-void DOTCONFDocument::setRequiredOptionNames(const char ** requiredOptionNames)
-{
- while(*requiredOptionNames){
- requiredOptions.push_back(strdup( *requiredOptionNames ));
- requiredOptionNames++;
- }
-}
-