avoid ascii control characters in strings
[dana/openbox.git] / obt / ddfile.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    obt/ddfile.c for the Openbox window manager
4    Copyright (c) 2009        Dana Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "obt/ddfile.h"
20 #include <glib.h>
21 #ifdef HAVE_STRING_H
22 #include <string.h>
23 #endif
24 #ifdef HAVE_STDIO_H
25 #include <stdio.h>
26 #endif
27
28 typedef struct _ObtDDParse {
29     gchar *filename;
30     gulong lineno;
31 } ObtDDParse;
32
33 typedef enum {
34     DATA_STRING,
35     DATA_LOCALESTRING,
36     DATA_BOOLEAN,
37     DATA_NUMERIC,
38     NUM_DATA_TYPES
39 } ObtDDDataType;
40
41 struct _ObtDDFile {
42     guint ref;
43
44     ObtDDFileType type;
45     gchar *name; /*!< Specific name for the object (eg Firefox) */
46     gchar *generic; /*!< Generic name for the object (eg Web Browser) */
47     gchar *comment; /*!< Comment/description to display for the object */
48     gchar *icon; /*!< Name/path for an icon for the object */
49
50     union _ObtDDFileData {
51         struct {
52             gchar *exec; /*!< Executable to run for the app */
53             gchar *wdir; /*!< Working dir to run the app in */
54             gboolean term; /*!< Run the app in a terminal or not */
55             ObtDDFileAppOpen open;
56
57             /* XXX gchar**? or something better, a mime struct.. maybe
58                glib has something i can use. */
59             gchar **mime; /*!< Mime types the app can open */
60
61             ObtDDFileAppStartup startup;
62             gchar *startup_wmclass;
63         } app;
64         struct {
65             gchar *url;
66         } link;
67         struct {
68         } dir;
69     } d;
70 };
71
72 static void parse_error(const gchar *m, const ObtDDParse *const parse,
73                         gboolean *error)
74 {
75     if (!parse->filename)
76         g_warning("%s at line %lu of input\n", m, parse->lineno);
77     else
78         g_warning("%s at line %lu of file %s\n",
79                   m, parse->lineno, parse->filename);
80     if (error) *error = TRUE;
81 }
82
83 /* reads an input string, strips out invalid stuff, and parses
84    backslash-stuff */
85 static gchar* parse_string(const gchar *in, gboolean locale,
86                            const ObtDDParse *const parse,
87                            gboolean *error)
88 {
89     const gint bytes = strlen(in);
90     gboolean backslash;
91     gchar *out, *o;
92     const gchar *end, *i;
93
94     g_return_val_if_fail(in != NULL, NULL);
95
96     if (!locale) {
97         end = in + bytes;
98         for (i = in; i < end; ++i) {
99             if (*i > 126 || *i < 32) { /* non-control character ascii */
100                 end = i;
101                 parse_error("Invalid bytes in string", parse, error);
102                 break;
103             }
104         }
105     }
106     else if (!g_utf8_validate(in, bytes, &end))
107         parse_error("Invalid bytes in localestring", parse, error);
108
109     out = g_new(char, bytes + 1);
110     i = in; o = out;
111     backslash = FALSE;
112     while (i < end) {
113         const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1;
114         if (backslash) {
115             switch(*i) {
116             case 's': *o++ = ' '; break;
117             case 'n': *o++ = '\n'; break;
118             case 't': *o++ = '\t'; break;
119             case 'r': *o++ = '\r'; break;
120             case '\\': *o++ = '\\'; break;
121             default:
122                 parse_error((locale ?
123                              "Invalid escape sequence in localestring" :
124                              "Invalid escape sequence in string"),
125                             parse, error);
126             }
127             backslash = FALSE;
128         }
129         else if (*i == '\\')
130             backslash = TRUE;
131         else if (*i >= 127 || *i < 32) { /* avoid ascii control characters */
132             parse_error("Found control character in string", parse, error);
133             break;
134         }
135         else {
136             memcpy(o, i, next-i);
137             o += next-i;
138         }
139         i = next;
140     }
141     *o = '\0';
142     return o;
143 }
144
145 static gboolean parse_bool(const gchar *in, const ObtDDParse *const parse,
146                            gboolean *error)
147 {
148     if (strcmp(in, "true") == 0)
149         return TRUE;
150     else if (strcmp(in, "false") != 0)
151         parse_error("Invalid boolean value", parse, error);
152     return FALSE;
153 }
154
155 static float parse_numeric(const gchar *in, const ObtDDParse *const parse,
156     gboolean *error)
157 {
158     float out = 0;
159     if (sscanf(in, "%f", &out) == 0)
160         parse_error("Invalid numeric value", parse, error);
161     return out;
162 }
163
164 gboolean parse_file_line(FILE *f, gchar **buf, gulong *size, gulong *read,
165                          ObtDDParse *parse, gboolean *error)
166 {
167     const gulong BUFMUL = 80;
168     size_t ret;
169     gulong i, null;
170
171     if (*size == 0) {
172         g_assert(*read == 0);
173         *size = BUFMUL;
174         *buf = g_new(char, *size);
175     }
176
177     /* remove everything up to a null zero already in the buffer and shift
178        the rest to the front */
179     null = *size;
180     for (i = 0; i < *read; ++i) {
181         if (null < *size)
182             (*buf)[i-null-1] = (*buf)[i];
183         else if ((*buf)[i] == '\0')
184             null = i;
185     }
186     if (null < *size)
187         *read -= null + 1;
188
189     /* is there already a newline in the buffer? */
190     for (i = 0; i < *read; ++i)
191         if ((*buf)[i] == '\n') {
192             /* turn it into a null zero and done */
193             (*buf)[i] = '\0';
194             return TRUE;
195         }
196
197     /* we need to read some more to find a newline */
198     while (TRUE) {
199         gulong eol;
200         gchar *newread;
201
202         newread = *buf + *read;
203         ret = fread(newread, sizeof(char), *size-*read, f);
204         if (ret < *size - *read && !feof(f)) {
205             parse_error("Error reading", parse, error);
206             return FALSE;
207         }
208         *read += ret;
209
210         /* strip out null zeros in the input and look for an endofline */
211         null = 0;
212         eol = *size;
213         for (i = newread-*buf; i < *read; ++i) {
214             if (null > 0)
215                 (*buf)[i] = (*buf)[i+null];
216             if ((*buf)[i] == '\0') {
217                 ++null;
218                 --(*read);
219                 --i; /* try again */
220             }
221             else if ((*buf)[i] == '\n' && eol == *size) {
222                 eol = i;
223                 /* turn it into a null zero */
224                 (*buf)[i] = '\0';
225             }
226         }
227
228         if (eol != *size)
229             /* found an endofline, done */
230             break;
231         else if (feof(f) && *read < *size) {
232             /* found the endoffile, done (if there is space) */
233             if (*read > 0) {
234                 /* stick a null zero on if there is test on the last line */
235                 (*buf)[(*read)++] = '\0';
236             }
237             break;
238         }
239         else {
240             /* read more */
241             size += BUFMUL;
242             *buf = g_renew(char, *buf, *size);
243         }
244     }
245     return *read > 0;
246 }
247
248 static gboolean parse_file(ObtDDFile *dd, FILE *f, ObtDDParse *parse)
249 {
250     gchar *buf = NULL;
251     gulong bytes = 0, read = 0;
252     gboolean error = FALSE;
253
254     while (parse_file_line(f, &buf, &bytes, &read, parse, &error)) {
255         /* XXX use the string in buf */
256         ++parse->lineno;
257     }
258
259     if (buf) g_free(buf);
260     return !error;
261 }
262
263 ObtDDFile* obt_ddfile_new_from_file(const gchar *name, GSList *paths)
264 {
265     ObtDDFile *dd;
266     ObtDDParse parse;
267     GSList *it;
268     FILE *f;
269
270     dd = g_slice_new(ObtDDFile);
271     dd->ref = 1;
272
273     f = NULL;
274     for (it = paths; it && !f; it = g_slist_next(it)) {
275         gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name);
276         if ((f = fopen(path, "r"))) {
277             parse.filename = path;
278             parse.lineno = 0;
279             if (!parse_file(dd, f, &parse)) f = NULL;
280         }
281     }
282     if (!f) {
283         obt_ddfile_unref(dd);
284         dd = NULL;
285     }
286     return dd;
287 }
288
289 void obt_ddfile_ref(ObtDDFile *dd)
290 {
291     ++dd->ref;
292 }
293
294 void obt_ddfile_unref(ObtDDFile *dd)
295 {
296     if (--dd->ref < 1) {
297         g_slice_free(ObtDDFile, dd);
298     }
299 }