summaryrefslogtreecommitdiff
path: root/src/libmowgli/ext/confparse.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libmowgli/ext/confparse.c')
-rw-r--r--src/libmowgli/ext/confparse.c436
1 files changed, 436 insertions, 0 deletions
diff --git a/src/libmowgli/ext/confparse.c b/src/libmowgli/ext/confparse.c
new file mode 100644
index 0000000..898ba8c
--- /dev/null
+++ b/src/libmowgli/ext/confparse.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2005-2011 Atheme Project (http://www.atheme.org)
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Description of config files parsed by this:
+ *
+ * configfile = *WS *configentry
+ * configentry = value [1*WS value] [1*WS "{" *(configentry 1*WS) "}" ] *WS ";"
+ * value = 1*achar / DQUOTE *qchar DQUOTE
+ * achar = <any CHAR except WS or DQUOTE>
+ * qchar = <any CHAR except DQUOTE or \> / "\\" / "\" DQUOTE
+ * comment = "/" "*" <anything except * /> "*" "/" /
+ * "#" *CHAR %0x0A /
+ * "//" *CHAR %0x0A
+ * WS = %x09 / %x0A / %x0D / SPACE / "=" / comment
+ *
+ * A value of "include" for toplevel configentries causes a file to be
+ * included. The included file is logically appended to the current file,
+ * no matter where the include directive is. Include files must have balanced
+ * braces.
+ */
+/*
+ * Original idea from the csircd config parser written by Fred Jacobs
+ * and Chris Behrens.
+ */
+
+#include "mowgli.h"
+#include <sys/stat.h>
+#include <limits.h>
+
+#define MAX_INCLUDE_NESTING 16
+
+static mowgli_config_file_t *mowgli_config_file_parse(const char *filename, char *confdata);
+static void mowgli_config_file_entry_free(mowgli_config_file_entry_t *ceptr);
+static mowgli_config_file_t *mowgli_config_file_load_internal(mowgli_config_file_t *parent, const char *filename);
+
+#define CF_ERRORED(cf) ((cf)->curline <= 0)
+
+static void mowgli_config_file_error(mowgli_config_file_t *cf, const char *format, ...)
+{
+ va_list ap;
+ char buffer[1024];
+ char *ptr;
+
+ va_start(ap, format);
+ vsnprintf(buffer, sizeof buffer, format, ap);
+ va_end(ap);
+
+ if ((ptr = strchr(buffer, '\n')) != NULL)
+ *ptr = '\0';
+
+ if (cf != NULL)
+ {
+ if (cf->curline < 0)
+ cf->curline = -cf->curline;
+
+ mowgli_log("%s:%d: %s", cf->filename, cf->curline, buffer);
+
+ /* mark config parse as failed */
+ cf->curline = -cf->curline;
+ }
+ else
+ mowgli_log("mowgli_config_file_parse(): %s", buffer);
+}
+
+static void skip_ws(char **pos, mowgli_config_file_t *cf)
+{
+ int startline;
+
+ for (;;)
+ {
+ switch (**pos)
+ {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '=': /* XXX */
+ break;
+ case '\n':
+ cf->curline++;
+ break;
+ case '/':
+ if ((*pos)[1] == '*')
+ {
+ startline = cf->curline;
+ (*pos)++;
+ (*pos)++;
+ while (**pos != '\0' && (**pos != '*' || (*pos)[1] != '/'))
+ {
+ if (**pos == '\n')
+ cf->curline++;
+ (*pos)++;
+ }
+ if (**pos == '\0')
+ mowgli_config_file_error(cf, "File ends inside comment starting at line %d", startline);
+ else
+ (*pos)++; /* skip '*' */
+ }
+ else if ((*pos)[1] == '/')
+ {
+ while (**pos != '\0' && **pos != '\n' && **pos != '\r')
+ (*pos)++;
+ continue;
+ }
+ else
+ return;
+ break;
+ case '#':
+ while (**pos != '\0' && **pos != '\n' && **pos != '\r')
+ (*pos)++;
+ continue;
+ default:
+ return;
+ }
+ if (**pos == '\0')
+ return;
+ (*pos)++;
+ }
+}
+
+static char *get_value(char **pos, mowgli_config_file_t *cf, char *skipped)
+{
+ char *p = *pos;
+ char *q;
+ char *start;
+
+ *skipped = '\0';
+ if (*p == '"')
+ {
+ p++;
+ start = p;
+ q = p;
+ while (*p != '\0' && *p != '\r' && *p != '\n' && *p != '"')
+ {
+ if (*p == '\\' && (p[1] == '"' || p[1] == '\\'))
+ p++;
+ *q++ = *p++;
+ }
+ if (*p == '\0')
+ {
+ mowgli_config_file_error(cf, "File ends inside quoted string");
+ return NULL;
+ }
+ if (*p == '\r' || *p == '\n')
+ {
+ mowgli_config_file_error(cf, "Newline inside quoted string");
+ return NULL;
+ }
+ if (*p != '"')
+ {
+ mowgli_config_file_error(cf, "Weird character terminating quoted string (BUG)");
+ return NULL;
+ }
+ p++;
+ *q = '\0';
+ *pos = p;
+ skip_ws(pos, cf);
+ return start;
+ }
+ else
+ {
+ start = p;
+ while (*p != '\0' && *p != '\t' && *p != '\r' && *p != '\n' &&
+ *p != ' ' && *p != '/' && *p != '#' &&
+ *p != ';' && *p != '{' && *p != '}')
+ p++;
+ if (p == start)
+ return NULL;
+ *pos = p;
+ skip_ws(pos, cf);
+ if (p == *pos)
+ *skipped = *p;
+ *p = '\0';
+ if (p == *pos)
+ (*pos)++;
+ return start;
+ }
+}
+
+static mowgli_config_file_t *mowgli_config_file_parse(const char *filename, char *confdata)
+{
+ mowgli_config_file_t *cf, *subcf, *lastcf;
+ mowgli_config_file_entry_t **pprevce, *ce, *upce;
+ char *p, *val;
+ char c;
+
+ cf = mowgli_alloc(sizeof *cf);
+ cf->filename = mowgli_strdup(filename);
+ cf->curline = 1;
+ cf->mem = confdata;
+ lastcf = cf;
+ pprevce = &cf->entries;
+ upce = NULL;
+ p = confdata;
+ while (*p != '\0')
+ {
+ skip_ws(&p, cf);
+ if (*p == '\0' || CF_ERRORED(cf))
+ break;
+ if (*p == '}')
+ {
+ if (upce == NULL)
+ {
+ mowgli_config_file_error(cf, "Extraneous closing brace");
+ break;
+ }
+ ce = upce;
+ ce->sectlinenum = cf->curline;
+ pprevce = &ce->next;
+ upce = ce->prevlevel;
+ p++;
+ skip_ws(&p, cf);
+ if (CF_ERRORED(cf))
+ break;
+ if (*p != ';')
+ {
+ mowgli_config_file_error(cf, "Missing semicolon after closing brace for section ending at line %d", ce->sectlinenum);
+ break;
+ }
+ ce = NULL;
+ p++;
+ continue;
+ }
+ val = get_value(&p, cf, &c);
+ if (CF_ERRORED(cf))
+ break;
+ if (val == NULL)
+ {
+ mowgli_config_file_error(cf, "Unexpected character trying to read variable name");
+ break;
+ }
+ ce = mowgli_alloc(sizeof *ce);
+ ce->fileptr = cf;
+ ce->varlinenum = cf->curline;
+ ce->varname = val;
+ ce->prevlevel = upce;
+ *pprevce = ce;
+ pprevce = &ce->next;
+ if (c == '\0' && (*p == '{' || *p == ';'))
+ c = *p++;
+ if (c == '{')
+ {
+ pprevce = &ce->entries;
+ upce = ce;
+ ce = NULL;
+ }
+ else if (c == ';')
+ {
+ ce = NULL;
+ }
+ else if (c != '\0')
+ {
+ mowgli_config_file_error(cf, "Unexpected characters after unquoted string %s", ce->varname);
+ break;
+ }
+ else
+ {
+ val = get_value(&p, cf, &c);
+ if (CF_ERRORED(cf))
+ break;
+ if (val == NULL)
+ {
+ mowgli_config_file_error(cf, "Unexpected character trying to read value for %s", ce->varname);
+ break;
+ }
+ ce->vardata = val;
+ if (c == '\0' && (*p == '{' || *p == ';'))
+ c = *p++;
+ if (c == '{')
+ {
+ pprevce = &ce->entries;
+ upce = ce;
+ ce = NULL;
+ }
+ else if (c == ';')
+ {
+ if (upce == NULL && !strcasecmp(ce->varname, "include"))
+ {
+ subcf = mowgli_config_file_load_internal(cf, ce->vardata);
+ if (subcf == NULL)
+ {
+ mowgli_config_file_error(cf, "Error in file included from here");
+ break;
+ }
+ lastcf->next = subcf;
+ while (lastcf->next != NULL)
+ lastcf = lastcf->next;
+ }
+ ce = NULL;
+ }
+ else
+ {
+ mowgli_config_file_error(cf, "Unexpected characters after value %s %s", ce->varname, ce->vardata);
+ break;
+ }
+ }
+ }
+ if (!CF_ERRORED(cf) && upce != NULL)
+ {
+ mowgli_config_file_error(cf, "One or more sections not closed");
+ ce = upce;
+ while (ce->prevlevel != NULL)
+ ce = ce->prevlevel;
+ if (ce->vardata != NULL)
+ mowgli_config_file_error(cf, "First unclosed section is %s %s at line %d",
+ ce->varname, ce->vardata, ce->varlinenum);
+ else
+ mowgli_config_file_error(cf, "First unclosed section is %s at line %d",
+ ce->varname, ce->varlinenum);
+ }
+ if (CF_ERRORED(cf))
+ {
+ mowgli_config_file_free(cf);
+ cf = NULL;
+ }
+ return cf;
+}
+
+static void mowgli_config_file_entry_free(mowgli_config_file_entry_t *ceptr)
+{
+ mowgli_config_file_entry_t *nptr;
+
+ for (; ceptr; ceptr = nptr)
+ {
+ nptr = ceptr->next;
+ if (ceptr->entries)
+ mowgli_config_file_entry_free(ceptr->entries);
+ /* ce_varname and ce_vardata are inside cf_mem */
+ mowgli_free(ceptr);
+ }
+}
+
+void mowgli_config_file_free(mowgli_config_file_t *cfptr)
+{
+ mowgli_config_file_t *nptr;
+
+ for (; cfptr; cfptr = nptr)
+ {
+ nptr = cfptr->next;
+ if (cfptr->entries)
+ mowgli_config_file_entry_free(cfptr->entries);
+ mowgli_free(cfptr->filename);
+ mowgli_free(cfptr->mem);
+ mowgli_free(cfptr);
+ }
+}
+
+static mowgli_config_file_t *mowgli_config_file_load_internal(mowgli_config_file_t *parent, const char *filename)
+{
+ struct stat sb;
+ FILE *fp;
+ size_t ret;
+ char *buf = NULL;
+ mowgli_config_file_t *cfptr;
+ static int nestcnt;
+
+ if (nestcnt > MAX_INCLUDE_NESTING)
+ {
+ mowgli_config_file_error(parent, "Includes nested too deep \"%s\"\n", filename);
+ return NULL;
+ }
+
+ fp = fopen(filename, "rb");
+ if (!fp)
+ {
+ mowgli_config_file_error(parent, "Couldn't open \"%s\": %s\n", filename, strerror(errno));
+ return NULL;
+ }
+ if (stat(filename, &sb) == -1)
+ {
+ mowgli_config_file_error(parent, "Couldn't fstat \"%s\": %s\n", filename, strerror(errno));
+ fclose(fp);
+ return NULL;
+ }
+ if (!S_ISREG(sb.st_mode))
+ {
+ mowgli_config_file_error(parent, "Not a regular file: \"%s\"\n", filename);
+ fclose(fp);
+ return NULL;
+ }
+ if (sb.st_size > SSIZE_MAX - 1)
+ {
+ mowgli_config_file_error(parent, "File too large: \"%s\"\n", filename);
+ fclose(fp);
+ return NULL;
+ }
+ buf = (char *) mowgli_alloc(sb.st_size + 1);
+ if (sb.st_size)
+ {
+ errno = 0;
+ ret = fread(buf, 1, sb.st_size, fp);
+ if (ret != (size_t)sb.st_size)
+ {
+ mowgli_config_file_error(parent, "Error reading \"%s\": %s\n", filename, strerror(errno ? errno : EFAULT));
+ mowgli_free(buf);
+ fclose(fp);
+ return NULL;
+ }
+ }
+ else
+ ret = 0;
+ buf[ret] = '\0';
+ fclose(fp);
+ nestcnt++;
+ cfptr = mowgli_config_file_parse(filename, buf);
+ nestcnt--;
+ /* buf is owned by cfptr or freed now */
+ return cfptr;
+}
+
+mowgli_config_file_t *mowgli_config_file_load(const char *filename)
+{
+ return mowgli_config_file_load_internal(NULL, filename);
+}
+
+/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
+ * vim:ts=8
+ * vim:sw=8
+ * vim:noexpandtab
+ */