Update webpage, add some screenshots
[browser-switch] / configfile.c
1 /*
2  * configfile.c -- config file functions for browser-switchboard
3  *
4  * Copyright (C) 2009 Steven Luo
5  * Derived from a Python implementation by Jason Simpson and Steven Luo
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20  * USA.
21  */
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <sys/types.h>
27 #include <regex.h>
28
29 #include "configfile.h"
30
31 #define MAXLINE 1024
32
33 /* regex matching blank lines or comments */
34 #define REGEX_IGNORE "^[[:space:]]*(#|$)"
35 #define REGEX_IGNORE_FLAGS REG_EXTENDED|REG_NOSUB
36
37 /* regex matching foo = "bar", with arbitrary whitespace at beginning and end
38    of line and surrounding the = */
39 #define REGEX_CONFIG1 "^[[:space:]]*([^=[:space:]]+)[[:space:]]*=[[:space:]]*\"(.*)\"[[:space:]]*$"
40 #define REGEX_CONFIG1_FLAGS REG_EXTENDED
41
42 /* regex matching foo = bar, with arbitrary whitespace at beginning of line and
43    surrounding the = */
44 #define REGEX_CONFIG2 "^[[:space:]]*([^=[:space:]]+)[[:space:]]*=[[:space:]]*(.*)$"
45 #define REGEX_CONFIG2_FLAGS REG_EXTENDED|REG_NEWLINE
46
47 static regex_t re_ignore, re_config1, re_config2;
48 static int re_init = 0;
49
50 /* Open config file for reading */
51 FILE *open_config_file(void) {
52         char *homedir, *configfile;
53         size_t len;
54         FILE *fp;
55
56         /* Put together the path to the config file */
57         if (!(homedir = getenv("HOME")))
58                 homedir = DEFAULT_HOMEDIR;
59         len = strlen(homedir) + strlen(CONFIGFILE_LOC) + 1;
60         if (!(configfile = calloc(len, sizeof(char))))
61                 return NULL;
62         snprintf(configfile, len, "%s%s", homedir, CONFIGFILE_LOC);
63
64         /* Try to open the config file */
65         if (!(fp = fopen(configfile, "r"))) {
66                 /* Try the legacy config file location before giving up
67                    XXX we assume here that CONFIGFILE_LOC_OLD is shorter
68                    than CONFIGFILE_LOC! */
69                 snprintf(configfile, len, "%s%s", homedir, CONFIGFILE_LOC_OLD);
70                 fp = fopen(configfile, "r");
71         }
72
73         free(configfile);
74         return fp;
75 }
76
77 /* Initialize the config file parser
78    Returns 1 if initialization successful, 0 otherwise */
79 int parse_config_file_begin(void) {
80         /* Just return if parser's already been initialized */
81         if (re_init)
82                 return 1;
83
84         /* compile regex matching blank lines or comments */
85         if (regcomp(&re_ignore, REGEX_IGNORE, REGEX_IGNORE_FLAGS))
86                 return 0;
87         /* compile regex matching foo = "bar", with arbitrary whitespace at
88            beginning and end of line and surrounding the = */
89         if (regcomp(&re_config1, REGEX_CONFIG1, REGEX_CONFIG1_FLAGS)) {
90                 regfree(&re_ignore);
91                 return 0;
92         }
93         /* compile regex matching foo = bar, with arbitrary whitespace at
94            beginning of line and surrounding the = */
95         if (regcomp(&re_config2, REGEX_CONFIG2, REGEX_CONFIG2_FLAGS)) {
96                 regfree(&re_ignore);
97                 regfree(&re_config1);
98                 return 0;
99         }
100
101         re_init = 1;
102         return 1;
103 }
104
105 /* End config file parsing and free the resources */
106 void parse_config_file_end(void) {
107         /* Just return if parser's not active */
108         if (!re_init)
109                 return;
110
111         regfree(&re_ignore);
112         regfree(&re_config1);
113         regfree(&re_config2);
114         re_init = 0;
115 }
116
117 /* Read the next line from a config file and store it into a swb_config_line,
118    parsing it into key and value if possible
119    Caller is responsible for freeing the strings in the swb_config_line if
120    call returns successfully
121    Returns 0 on success (whether line parsed or not), 1 on EOF, -1 on error */
122 int parse_config_file_line(FILE *fp, struct swb_config_line *line) {
123         regmatch_t substrs[3];
124         size_t len;
125         char *tmp;
126
127         if (!re_init || !fp || !line)
128                 return -1;
129
130         line->parsed = 0;
131         line->key = NULL;
132         line->value = NULL;
133
134         if (!(line->key = calloc(MAXLINE, sizeof(char))))
135                 return -1;
136
137         /* Read in the next line of the config file
138            XXX doesn't deal with lines longer than MAXLINE */
139         if (!fgets(line->key, MAXLINE, fp)) {
140                 free(line->key);
141                 line->key = NULL;
142                 if (feof(fp))
143                         return 1;
144                 else
145                         return -1;
146         }
147
148         /* no need to parse blank lines and comments */
149         if (!regexec(&re_ignore, line->key, 0, NULL, 0))
150                 goto finish;
151
152         /* Find the substrings corresponding to the key and value
153            If the line doesn't match our idea of a config file entry,
154            don't parse it */
155         if (regexec(&re_config1, line->key, 3, substrs, 0) &&
156             regexec(&re_config2, line->key, 3, substrs, 0))
157                 goto finish;
158         if (substrs[1].rm_so == -1 || substrs[2].rm_so == -1)
159                 goto finish;
160
161         /* copy the config value into a new string */
162         len = substrs[2].rm_eo - substrs[2].rm_so;
163         if (!(line->value = calloc(len+1, sizeof(char)))) {
164                 free(line->key);
165                 line->key = NULL;
166                 return -1;
167         }
168         strncpy(line->value, line->key+substrs[2].rm_so, len);
169         /* calloc() zeroes the memory, so string is automatically
170            null terminated */
171
172         /* make key point to a null-terminated string holding the 
173            config key */
174         len = substrs[1].rm_eo - substrs[1].rm_so;
175         memmove(line->key, line->key+substrs[1].rm_so, len);
176         line->key[len] = '\0';
177
178         /* done parsing the line */
179         line->parsed = 1;
180
181 finish:
182         if (!line->parsed) {
183                 /* Toss a trailing newline, if present */
184                 len = strlen(line->key);
185                 if (line->key[len-1] == '\n')
186                         line->key[len-1] = '\0';
187         }
188         /* Try to shrink the allocation for key, to save space if someone
189            wants to keep it around */
190         len = strlen(line->key);
191         if ((tmp = realloc(line->key, len+1)))
192                 line->key = tmp;
193         return 0;
194 }