blob: 3510f72c88e338eff7b68becc738858bc869b9d8 [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Regex and string management functions.
3 *
Willy Tarreauf4f04122010-01-28 18:10:50 +01004 * Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
Willy Tarreaubaaee002006-06-26 02:48:02 +02005 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <ctype.h>
14#include <stdlib.h>
15#include <string.h>
16
Willy Tarreau4c7e4b72020-05-27 12:58:42 +020017#include <haproxy/api.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020018#include <haproxy/log.h>
Willy Tarreau7cd8b6e2020-06-02 17:32:26 +020019#include <haproxy/regex.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020020#include <haproxy/tools.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020021
22/* regex trash buffer used by various regex tests */
Emeric Brun272e2522017-06-15 11:53:49 +020023THREAD_LOCAL regmatch_t pmatch[MAX_MATCH]; /* rm_so, rm_eo for regular expressions */
Willy Tarreaubaaee002006-06-26 02:48:02 +020024
Willy Tarreauc8746532014-05-28 23:05:07 +020025int exp_replace(char *dst, unsigned int dst_size, char *src, const char *str, const regmatch_t *matches)
Willy Tarreaubaaee002006-06-26 02:48:02 +020026{
27 char *old_dst = dst;
Sasha Pachevc6002042014-05-26 12:33:48 -060028 char* dst_end = dst + dst_size;
Willy Tarreaubaaee002006-06-26 02:48:02 +020029
30 while (*str) {
31 if (*str == '\\') {
32 str++;
Sasha Pachevc6002042014-05-26 12:33:48 -060033 if (!*str)
34 return -1;
35
Willy Tarreau8f8e6452007-06-17 21:51:38 +020036 if (isdigit((unsigned char)*str)) {
Willy Tarreaubaaee002006-06-26 02:48:02 +020037 int len, num;
38
39 num = *str - '0';
40 str++;
41
42 if (matches[num].rm_eo > -1 && matches[num].rm_so > -1) {
43 len = matches[num].rm_eo - matches[num].rm_so;
Sasha Pachevc6002042014-05-26 12:33:48 -060044
45 if (dst + len >= dst_end)
46 return -1;
47
Willy Tarreaubaaee002006-06-26 02:48:02 +020048 memcpy(dst, src + matches[num].rm_so, len);
49 dst += len;
50 }
51
52 } else if (*str == 'x') {
53 unsigned char hex1, hex2;
54 str++;
55
Sasha Pachevc6002042014-05-26 12:33:48 -060056 if (!*str)
57 return -1;
58
Willy Tarreaubaaee002006-06-26 02:48:02 +020059 hex1 = toupper(*str++) - '0';
Sasha Pachevc6002042014-05-26 12:33:48 -060060
61 if (!*str)
62 return -1;
63
Willy Tarreaubaaee002006-06-26 02:48:02 +020064 hex2 = toupper(*str++) - '0';
65
66 if (hex1 > 9) hex1 -= 'A' - '9' - 1;
67 if (hex2 > 9) hex2 -= 'A' - '9' - 1;
Sasha Pachevc6002042014-05-26 12:33:48 -060068
69 if (dst >= dst_end)
70 return -1;
71
Willy Tarreaubaaee002006-06-26 02:48:02 +020072 *dst++ = (hex1<<4) + hex2;
73 } else {
Sasha Pachevc6002042014-05-26 12:33:48 -060074 if (dst >= dst_end)
75 return -1;
76
Willy Tarreaubaaee002006-06-26 02:48:02 +020077 *dst++ = *str++;
78 }
79 } else {
Sasha Pachevc6002042014-05-26 12:33:48 -060080 if (dst >= dst_end)
81 return -1;
82
Willy Tarreaubaaee002006-06-26 02:48:02 +020083 *dst++ = *str++;
84 }
85 }
Sasha Pachevc6002042014-05-26 12:33:48 -060086 if (dst >= dst_end)
87 return -1;
88
Willy Tarreaubaaee002006-06-26 02:48:02 +020089 *dst = '\0';
90 return dst - old_dst;
91}
92
93/* returns NULL if the replacement string <str> is valid, or the pointer to the first error */
Willy Tarreaub17916e2006-10-15 15:17:57 +020094const char *check_replace_string(const char *str)
Willy Tarreaubaaee002006-06-26 02:48:02 +020095{
Willy Tarreaub17916e2006-10-15 15:17:57 +020096 const char *err = NULL;
Willy Tarreaubaaee002006-06-26 02:48:02 +020097 while (*str) {
98 if (*str == '\\') {
99 err = str; /* in case of a backslash, we return the pointer to it */
100 str++;
101 if (!*str)
102 return err;
Willy Tarreau8f8e6452007-06-17 21:51:38 +0200103 else if (isdigit((unsigned char)*str))
Willy Tarreaubaaee002006-06-26 02:48:02 +0200104 err = NULL;
105 else if (*str == 'x') {
106 str++;
107 if (!ishex(*str))
108 return err;
109 str++;
110 if (!ishex(*str))
111 return err;
112 err = NULL;
113 }
114 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100115 ha_warning("'\\%c' : deprecated use of a backslash before something not '\\','x' or a digit.\n", *str);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200116 err = NULL;
117 }
118 }
119 str++;
120 }
121 return err;
122}
123
124
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200125/* This function apply regex. It take const null terminated char as input.
126 * If the function doesn't match, it returns false, else it returns true.
127 * When it is compiled with JIT, this function execute strlen on the subject.
Willy Tarreau15a53a42015-01-21 13:39:42 +0100128 * Currently the only supported flag is REG_NOTBOL.
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200129 */
130int regex_exec_match(const struct my_regex *preg, const char *subject,
Willy Tarreau15a53a42015-01-21 13:39:42 +0100131 size_t nmatch, regmatch_t pmatch[], int flags) {
David Carlierf2592b22016-11-21 21:25:58 +0000132#if defined(USE_PCRE) || defined(USE_PCRE_JIT) || defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200133 int ret;
David Carlierf2592b22016-11-21 21:25:58 +0000134#ifdef USE_PCRE2
135 PCRE2_SIZE *matches;
136 pcre2_match_data *pm;
137#else
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200138 int matches[MAX_MATCH * 3];
David Carlierf2592b22016-11-21 21:25:58 +0000139#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200140 int enmatch;
141 int i;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100142 int options;
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200143
144 /* Silently limit the number of allowed matches. max
145 * match i the maximum value for match, in fact this
146 * limit is not applyied.
147 */
David Carlierf2592b22016-11-21 21:25:58 +0000148
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200149 enmatch = nmatch;
150 if (enmatch > MAX_MATCH)
151 enmatch = MAX_MATCH;
152
Willy Tarreau15a53a42015-01-21 13:39:42 +0100153 options = 0;
154 if (flags & REG_NOTBOL)
David Carlierf2592b22016-11-21 21:25:58 +0000155#ifdef USE_PCRE2
156 options |= PCRE2_NOTBOL;
157#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100158 options |= PCRE_NOTBOL;
David Carlierf2592b22016-11-21 21:25:58 +0000159#endif
Willy Tarreau15a53a42015-01-21 13:39:42 +0100160
David Carlierf2592b22016-11-21 21:25:58 +0000161 /* The value returned by pcre_exec()/pcre2_match() is one more than the highest numbered
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200162 * pair that has been set. For example, if two substrings have been captured,
163 * the returned value is 3. If there are no capturing subpatterns, the return
164 * value from a successful match is 1, indicating that just the first pair of
165 * offsets has been set.
166 *
Joseph Herlanteda75482018-11-15 14:46:29 -0800167 * It seems that this function returns 0 if it detects more matches than available
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200168 * space in the matches array.
169 */
David Carlierf2592b22016-11-21 21:25:58 +0000170#ifdef USE_PCRE2
171 pm = pcre2_match_data_create_from_pattern(preg->reg, NULL);
172 ret = pcre2_match(preg->reg, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, options, pm, NULL);
173
174 if (ret < 0) {
175 pcre2_match_data_free(pm);
176 return 0;
177 }
178
179 matches = pcre2_get_ovector_pointer(pm);
180#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100181 ret = pcre_exec(preg->reg, preg->extra, subject, strlen(subject), 0, options, matches, enmatch * 3);
David Carlierf2592b22016-11-21 21:25:58 +0000182
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200183 if (ret < 0)
184 return 0;
David Carlierf2592b22016-11-21 21:25:58 +0000185#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200186
187 if (ret == 0)
188 ret = enmatch;
189
190 for (i=0; i<nmatch; i++) {
191 /* Copy offset. */
192 if (i < ret) {
193 pmatch[i].rm_so = matches[(i*2)];
194 pmatch[i].rm_eo = matches[(i*2)+1];
195 continue;
196 }
197 /* Set the unmatvh flag (-1). */
198 pmatch[i].rm_so = -1;
199 pmatch[i].rm_eo = -1;
200 }
David Carlierf2592b22016-11-21 21:25:58 +0000201#ifdef USE_PCRE2
202 pcre2_match_data_free(pm);
203#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200204 return 1;
205#else
206 int match;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100207
208 flags &= REG_NOTBOL;
209 match = regexec(&preg->regex, subject, nmatch, pmatch, flags);
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200210 if (match == REG_NOMATCH)
211 return 0;
212 return 1;
213#endif
214}
215
216/* This function apply regex. It take a "char *" ans length as input. The
217 * <subject> can be modified during the processing. If the function doesn't
218 * match, it returns false, else it returns true.
219 * When it is compiled with standard POSIX regex or PCRE, this function add
220 * a temporary null chracters at the end of the <subject>. The <subject> must
Willy Tarreau15a53a42015-01-21 13:39:42 +0100221 * have a real length of <length> + 1. Currently the only supported flag is
222 * REG_NOTBOL.
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200223 */
224int regex_exec_match2(const struct my_regex *preg, char *subject, int length,
Willy Tarreau15a53a42015-01-21 13:39:42 +0100225 size_t nmatch, regmatch_t pmatch[], int flags) {
David Carlierf2592b22016-11-21 21:25:58 +0000226#if defined(USE_PCRE) || defined(USE_PCRE_JIT) || defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200227 int ret;
David Carlierf2592b22016-11-21 21:25:58 +0000228#ifdef USE_PCRE2
229 PCRE2_SIZE *matches;
230 pcre2_match_data *pm;
231#else
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200232 int matches[MAX_MATCH * 3];
David Carlierf2592b22016-11-21 21:25:58 +0000233#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200234 int enmatch;
235 int i;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100236 int options;
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200237
238 /* Silently limit the number of allowed matches. max
239 * match i the maximum value for match, in fact this
240 * limit is not applyied.
241 */
242 enmatch = nmatch;
243 if (enmatch > MAX_MATCH)
244 enmatch = MAX_MATCH;
245
Willy Tarreau15a53a42015-01-21 13:39:42 +0100246 options = 0;
247 if (flags & REG_NOTBOL)
David Carlierf2592b22016-11-21 21:25:58 +0000248#ifdef USE_PCRE2
249 options |= PCRE2_NOTBOL;
250#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100251 options |= PCRE_NOTBOL;
David Carlierf2592b22016-11-21 21:25:58 +0000252#endif
Willy Tarreau15a53a42015-01-21 13:39:42 +0100253
David Carlierf2592b22016-11-21 21:25:58 +0000254 /* The value returned by pcre_exec()/pcre2_match() is one more than the highest numbered
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200255 * pair that has been set. For example, if two substrings have been captured,
256 * the returned value is 3. If there are no capturing subpatterns, the return
257 * value from a successful match is 1, indicating that just the first pair of
258 * offsets has been set.
259 *
Joseph Herlanteda75482018-11-15 14:46:29 -0800260 * It seems that this function returns 0 if it detects more matches than available
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200261 * space in the matches array.
262 */
David Carlierf2592b22016-11-21 21:25:58 +0000263#ifdef USE_PCRE2
264 pm = pcre2_match_data_create_from_pattern(preg->reg, NULL);
265 ret = pcre2_match(preg->reg, (PCRE2_SPTR)subject, (PCRE2_SIZE)length, 0, options, pm, NULL);
266
267 if (ret < 0) {
268 pcre2_match_data_free(pm);
269 return 0;
270 }
271
272 matches = pcre2_get_ovector_pointer(pm);
273#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100274 ret = pcre_exec(preg->reg, preg->extra, subject, length, 0, options, matches, enmatch * 3);
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200275 if (ret < 0)
276 return 0;
David Carlierf2592b22016-11-21 21:25:58 +0000277#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200278
279 if (ret == 0)
280 ret = enmatch;
281
282 for (i=0; i<nmatch; i++) {
283 /* Copy offset. */
284 if (i < ret) {
285 pmatch[i].rm_so = matches[(i*2)];
286 pmatch[i].rm_eo = matches[(i*2)+1];
287 continue;
288 }
289 /* Set the unmatvh flag (-1). */
290 pmatch[i].rm_so = -1;
291 pmatch[i].rm_eo = -1;
292 }
David Carlierf2592b22016-11-21 21:25:58 +0000293#ifdef USE_PCRE2
294 pcre2_match_data_free(pm);
295#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200296 return 1;
297#else
298 char old_char = subject[length];
299 int match;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100300
301 flags &= REG_NOTBOL;
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200302 subject[length] = 0;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100303 match = regexec(&preg->regex, subject, nmatch, pmatch, flags);
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200304 subject[length] = old_char;
305 if (match == REG_NOMATCH)
306 return 0;
307 return 1;
308#endif
309}
310
Dragan Dosen26743032019-04-30 15:54:36 +0200311struct my_regex *regex_comp(const char *str, int cs, int cap, char **err)
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200312{
Dragan Dosen26743032019-04-30 15:54:36 +0200313 struct my_regex *regex = NULL;
Thierry FOURNIER26202762014-06-18 11:50:51 +0200314#if defined(USE_PCRE) || defined(USE_PCRE_JIT)
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200315 int flags = 0;
316 const char *error;
317 int erroffset;
Dragan Dosen26743032019-04-30 15:54:36 +0200318#elif defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
319 int flags = 0;
320 int errn;
321#if defined(USE_PCRE2_JIT)
322 int jit;
323#endif
324 PCRE2_UCHAR error[256];
325 PCRE2_SIZE erroffset;
326#else
327 int flags = REG_EXTENDED;
328#endif
329
330 regex = calloc(1, sizeof(*regex));
331 if (!regex) {
332 memprintf(err, "not enough memory to build regex");
333 goto out_fail_alloc;
334 }
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200335
Dragan Dosen26743032019-04-30 15:54:36 +0200336#if defined(USE_PCRE) || defined(USE_PCRE_JIT)
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200337 if (!cs)
338 flags |= PCRE_CASELESS;
339 if (!cap)
340 flags |= PCRE_NO_AUTO_CAPTURE;
341
342 regex->reg = pcre_compile(str, flags, &error, &erroffset, NULL);
343 if (!regex->reg) {
344 memprintf(err, "regex '%s' is invalid (error=%s, erroffset=%d)", str, error, erroffset);
Dragan Dosen26743032019-04-30 15:54:36 +0200345 goto out_fail_alloc;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200346 }
347
348 regex->extra = pcre_study(regex->reg, PCRE_STUDY_JIT_COMPILE, &error);
Christian Ruppert955f4612014-10-29 17:05:53 +0100349 if (!regex->extra && error != NULL) {
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200350 pcre_free(regex->reg);
351 memprintf(err, "failed to compile regex '%s' (error=%s)", str, error);
Dragan Dosen26743032019-04-30 15:54:36 +0200352 goto out_fail_alloc;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200353 }
David Carlierf2592b22016-11-21 21:25:58 +0000354#elif defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
David Carlierf2592b22016-11-21 21:25:58 +0000355 if (!cs)
356 flags |= PCRE2_CASELESS;
357 if (!cap)
358 flags |= PCRE2_NO_AUTO_CAPTURE;
359
360 regex->reg = pcre2_compile((PCRE2_SPTR)str, PCRE2_ZERO_TERMINATED, flags, &errn, &erroffset, NULL);
361 if (!regex->reg) {
362 pcre2_get_error_message(errn, error, sizeof(error));
363 memprintf(err, "regex '%s' is invalid (error=%s, erroffset=%zu)", str, error, erroffset);
Dragan Dosen26743032019-04-30 15:54:36 +0200364 goto out_fail_alloc;
David Carlierf2592b22016-11-21 21:25:58 +0000365 }
366
367#if defined(USE_PCRE2_JIT)
368 jit = pcre2_jit_compile(regex->reg, PCRE2_JIT_COMPLETE);
369 /*
370 * We end if it is an error not related to lack of JIT support
371 * in a case of JIT support missing pcre2_jit_compile is "no-op"
372 */
373 if (jit < 0 && jit != PCRE2_ERROR_JIT_BADOPTION) {
374 pcre2_code_free(regex->reg);
375 memprintf(err, "regex '%s' jit compilation failed", str);
Dragan Dosen26743032019-04-30 15:54:36 +0200376 goto out_fail_alloc;
David Carlierf2592b22016-11-21 21:25:58 +0000377 }
378#endif
379
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200380#else
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200381 if (!cs)
382 flags |= REG_ICASE;
383 if (!cap)
384 flags |= REG_NOSUB;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200385
Thierry FOURNIER799c0422013-12-06 20:36:20 +0100386 if (regcomp(&regex->regex, str, flags) != 0) {
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200387 memprintf(err, "regex '%s' is invalid", str);
Dragan Dosen26743032019-04-30 15:54:36 +0200388 goto out_fail_alloc;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200389 }
390#endif
Dragan Dosen26743032019-04-30 15:54:36 +0200391 return regex;
392
393 out_fail_alloc:
394 free(regex);
395 return NULL;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200396}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200397
Willy Tarreau80713382018-11-26 10:19:54 +0100398static void regex_register_build_options(void)
Willy Tarreau7a9ac6d2016-12-21 19:13:14 +0100399{
400 char *ptr = NULL;
401
402#ifdef USE_PCRE
403 memprintf(&ptr, "Built with PCRE version : %s", (HAP_XSTRING(Z PCRE_PRERELEASE)[1] == 0)?
404 HAP_XSTRING(PCRE_MAJOR.PCRE_MINOR PCRE_DATE) :
405 HAP_XSTRING(PCRE_MAJOR.PCRE_MINOR) HAP_XSTRING(PCRE_PRERELEASE PCRE_DATE));
406 memprintf(&ptr, "%s\nRunning on PCRE version : %s", ptr, pcre_version());
407
408 memprintf(&ptr, "%s\nPCRE library supports JIT : %s", ptr,
409#ifdef USE_PCRE_JIT
410 ({
411 int r;
412 pcre_config(PCRE_CONFIG_JIT, &r);
413 r ? "yes" : "no (libpcre build without JIT?)";
414 })
415#else
416 "no (USE_PCRE_JIT not set)"
417#endif
418 );
David Carlierf2592b22016-11-21 21:25:58 +0000419#endif /* USE_PCRE */
420
421#ifdef USE_PCRE2
422 memprintf(&ptr, "Built with PCRE2 version : %s", (HAP_XSTRING(Z PCRE2_PRERELEASE)[1] == 0) ?
423 HAP_XSTRING(PCRE2_MAJOR.PCRE2_MINOR PCRE2_DATE) :
424 HAP_XSTRING(PCRE2_MAJOR.PCRE2_MINOR) HAP_XSTRING(PCRE2_PRERELEASE PCRE2_DATE));
425 memprintf(&ptr, "%s\nPCRE2 library supports JIT : %s", ptr,
426#ifdef USE_PCRE2_JIT
427 ({
428 int r;
429 pcre2_config(PCRE2_CONFIG_JIT, &r);
430 r ? "yes" : "no (libpcre2 build without JIT?)";
431 })
Willy Tarreau7a9ac6d2016-12-21 19:13:14 +0100432#else
David Carlierf2592b22016-11-21 21:25:58 +0000433 "no (USE_PCRE2_JIT not set)"
434#endif
435 );
436#endif /* USE_PCRE2 */
437
438#if !defined(USE_PCRE) && !defined(USE_PCRE2)
439 memprintf(&ptr, "Built without PCRE or PCRE2 support (using libc's regex instead)");
Willy Tarreau7a9ac6d2016-12-21 19:13:14 +0100440#endif
441 hap_register_build_opts(ptr, 1);
442}
443
Willy Tarreau80713382018-11-26 10:19:54 +0100444INITCALL0(STG_REGISTER, regex_register_build_options);
445
Willy Tarreaubaaee002006-06-26 02:48:02 +0200446/*
447 * Local variables:
448 * c-indent-level: 8
449 * c-basic-offset: 8
450 * End:
451 */