ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/src/match.C
Revision: 1.3
Committed: Sat Jul 21 13:23:22 2007 UTC (16 years, 10 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.2: +1 -1 lines
Log Message:
- added rcsid to some files
- more documentation tweaks
- made most protocol commands local to phandler.C
- added ircd metadata (inspircd only for now)
- added inspircd swhois support

File Contents

# Content
1 /*
2 * match.C: Casemapping and matching functions.
3 * Rights to this code are documented in doc/pod/license.pod.
4 *
5 * Copyright © 2005-2007 Atheme Project (http://www.atheme.org)
6 */
7
8 static char const rcsid[] = "$Id$";
9
10 #include "atheme.h"
11
12 #define BadPtr(x) (!(x) || (*(x) == '\0'))
13
14 int match_mapping = MATCH_RFC1459;
15
16 const unsigned char ToLowerTab[] = {
17 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
18 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14,
19 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
20 0x1e, 0x1f,
21 ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')',
22 '*', '+', ',', '-', '.', '/',
23 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
24 ':', ';', '<', '=', '>', '?',
25 '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
26 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
27 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
28 '_',
29 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
30 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
31 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
32 0x7f,
33 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
34 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
35 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
36 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
37 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
38 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
39 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
40 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
41 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
42 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
43 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
44 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
45 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
46 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
47 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
48 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
49 };
50
51 const unsigned char ToUpperTab[] = {
52 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
53 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14,
54 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
55 0x1e, 0x1f,
56 ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')',
57 '*', '+', ',', '-', '.', '/',
58 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
59 ':', ';', '<', '=', '>', '?',
60 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
61 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
62 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^',
63 0x5f,
64 '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
65 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
66 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^',
67 0x7f,
68 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
69 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
70 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
71 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
72 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
73 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
74 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
75 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
76 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
77 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
78 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
79 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
80 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
81 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
82 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
83 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
84 };
85
86 int
87 ToLower (int c)
88 {
89 if (match_mapping == MATCH_ASCII)
90 return tolower (c);
91 /* else */
92 return (ToLowerTab[(unsigned char) (c)]);
93 }
94
95 int
96 ToUpper (int c)
97 {
98 if (match_mapping == MATCH_ASCII)
99 return toupper (c);
100 /* else */
101 return (ToUpperTab[(unsigned char) (c)]);
102 }
103
104 void
105 set_match_mapping (int type)
106 {
107 match_mapping = type;
108 }
109
110 #define MAX_ITERATIONS 512
111 /*
112 ** Compare if a given string (name) matches the given
113 ** mask (which can contain wild cards: '*' - match any
114 ** number of chars, '?' - match any single character.
115 **
116 ** return 0, if match
117 ** 1, if no match
118 */
119
120 /*
121 ** match()
122 ** Iterative matching function, rather than recursive.
123 ** Written by Douglas A Lewis (dalewis@acsu.buffalo.edu)
124 */
125
126 int
127 match (const char *mask, const char *name)
128 {
129 const u_char *m = (const u_char *) mask, *n = (const u_char *) name;
130 const char *ma = mask, *na = name;
131 int wild = 0, q = 0, calls = 0;
132
133 if (!mask || !name)
134 return 1;
135
136 /* if the mask is "*", it matches everything */
137 if ((*m == '*') && (*(m + 1) == '\0'))
138 return 0;
139
140 while (1)
141 {
142 #ifdef MAX_ITERATIONS
143 if (calls++ > MAX_ITERATIONS)
144 break;
145 #endif
146
147 if (*m == '*')
148 {
149 while (*m == '*')
150 m++;
151 wild = 1;
152 ma = (const char *) m;
153 na = (const char *) n;
154 }
155
156 if (!*m)
157 {
158 if (!*n)
159 return 0;
160 for (m--; (m > (const u_char *) mask) && (*m == '?' || *m == '&' || *m == '#'); m--)
161 ;
162 if ((m > (const u_char *) mask) && (*m == '*') && (m[-1] != '\\'))
163 return 0;
164 if (!wild)
165 return 1;
166 m = (const u_char *) ma;
167 n = (const u_char *) ++na;
168 }
169 else if (!*n)
170 return 1;
171 if ((*m == '\\') && ((m[1] == '*') || (m[1] == '?') || (m[1] == '&') || (m[1] == '#') || (m[1] == '%')))
172 {
173 m++;
174 q = 1;
175 }
176 else
177 q = 0;
178
179 if ((ToLower (*m) != ToLower (*n)) && (((*m != '?') && !(*m == '&' && IsAlpha (*n)) && !(*m == '#' && IsDigit (*n)) && !(*m == '%' && IsNon (*n))) || q))
180 {
181 if (!wild)
182 return 1;
183 m = (const u_char *) ma;
184 n = (const u_char *) ++na;
185 }
186 else
187 {
188 if (*m)
189 m++;
190 if (*n)
191 n++;
192 }
193 }
194
195 return 1;
196 }
197
198
199 /*
200 ** collapse a pattern string into minimal components.
201 ** This particular version is "in place", so that it changes the pattern
202 ** which is to be reduced to a "minimal" size.
203 */
204 char *
205 collapse (char *pattern)
206 {
207 char *s = pattern, *s1, *t;
208
209 if (BadPtr (pattern))
210 return pattern;
211 /*
212 * Collapse all \** into \*, \*[?]+\** into \*[?]+
213 */
214 for (; *s; s++)
215 if (*s == '\\')
216 if (!*(s + 1))
217 break;
218 else
219 s++;
220 else if (*s == '*')
221 {
222 if (*(t = s1 = s + 1) == '*')
223 while (*t == '*')
224 t++;
225 else if (*t == '?')
226 for (t++, s1++; *t == '*' || *t == '?'; t++)
227 if (*t == '?')
228 *s1++ = *t;
229 while ((*s1++ = *t++))
230 ;
231 }
232 return pattern;
233 }
234
235 /*
236 ** Case insensitive comparison of two null terminated strings.
237 **
238 ** returns 0, if s1 equal to s2
239 ** <0, if s1 lexicographically less than s2
240 ** >0, if s1 lexicographically greater than s2
241 */
242 int
243 irccasecmp (const char *s1, const char *s2)
244 {
245 const unsigned char *str1 = (const unsigned char *) s1;
246 const unsigned char *str2 = (const unsigned char *) s2;
247 int res;
248
249 if (!s1 || !s2)
250 return -1;
251
252 if (match_mapping == MATCH_ASCII)
253 return strcasecmp (s1, s2);
254
255 while ((res = ToUpper (*str1) - ToUpper (*str2)) == 0)
256 {
257 if (*str1 == '\0')
258 return 0;
259 str1++;
260 str2++;
261 }
262 return (res);
263 }
264
265 int
266 ircncasecmp (const char *str1, const char *str2, int n)
267 {
268 const unsigned char *s1 = (const unsigned char *) str1;
269 const unsigned char *s2 = (const unsigned char *) str2;
270 int res;
271
272 if (match_mapping == MATCH_ASCII)
273 return strncasecmp (str1, str2, n);
274
275 while ((res = ToUpper (*s1) - ToUpper (*s2)) == 0)
276 {
277 s1++;
278 s2++;
279 n--;
280 if (n == 0 || (*s1 == '\0' && *s2 == '\0'))
281 return 0;
282 }
283 return (res);
284 }
285
286 const unsigned int charattrs[] = {
287 /* 0 */ 0,
288 /* 1 */ 0,
289 /* 2 */ 0,
290 /* 3 */ 0,
291 /* 4 */ 0,
292 /* 5 */ 0,
293 /* 6 */ 0,
294 /* 7 BEL */ 0,
295 /* 8 \b */ 0,
296 /* 9 \t */ 0,
297 /* 10 \n */ 0,
298 /* 11 \v */ 0,
299 /* 12 \f */ 0,
300 /* 13 \r */ 0,
301 /* 14 */ 0,
302 /* 15 */ 0,
303 /* 16 */ 0,
304 /* 17 */ 0,
305 /* 18 */ 0,
306 /* 19 */ 0,
307 /* 20 */ 0,
308 /* 21 */ 0,
309 /* 22 */ 0,
310 /* 23 */ 0,
311 /* 24 */ 0,
312 /* 25 */ 0,
313 /* 26 */ 0,
314 /* 27 */ 0,
315 /* 28 */ 0,
316 /* 29 */ 0,
317 /* 30 */ 0,
318 /* 31 */ 0,
319 /* SP */ 0,
320 /* ! */ 0,
321 /* " */ 0,
322 /* # */ 0,
323 /* $ */ 0,
324 /* % */ 0,
325 /* & */ 0,
326 /* ' */ 0,
327 /* ( */ 0,
328 /* ) */ 0,
329 /* * */ 0,
330 /* + */ 0,
331 /* , */ 0,
332 /* - */ 0,
333 /* . */ 0,
334 /* / */ 0,
335 /* 0 */ C_DIGIT,
336 /* 1 */ C_DIGIT,
337 /* 2 */ C_DIGIT,
338 /* 3 */ C_DIGIT,
339 /* 4 */ C_DIGIT,
340 /* 5 */ C_DIGIT,
341 /* 6 */ C_DIGIT,
342 /* 7 */ C_DIGIT,
343 /* 8 */ C_DIGIT,
344 /* 9 */ C_DIGIT,
345 /* : */ 0,
346 /* ; */ 0,
347 /* < */ 0,
348 /* = */ 0,
349 /* > */ 0,
350 /* ? */ 0,
351 /* @ */ 0,
352 /* A */ C_ALPHA,
353 /* B */ C_ALPHA,
354 /* C */ C_ALPHA,
355 /* D */ C_ALPHA,
356 /* E */ C_ALPHA,
357 /* F */ C_ALPHA,
358 /* G */ C_ALPHA,
359 /* H */ C_ALPHA,
360 /* I */ C_ALPHA,
361 /* J */ C_ALPHA,
362 /* K */ C_ALPHA,
363 /* L */ C_ALPHA,
364 /* M */ C_ALPHA,
365 /* N */ C_ALPHA,
366 /* O */ C_ALPHA,
367 /* P */ C_ALPHA,
368 /* Q */ C_ALPHA,
369 /* R */ C_ALPHA,
370 /* S */ C_ALPHA,
371 /* T */ C_ALPHA,
372 /* U */ C_ALPHA,
373 /* V */ C_ALPHA,
374 /* W */ C_ALPHA,
375 /* X */ C_ALPHA,
376 /* Y */ C_ALPHA,
377 /* Z */ C_ALPHA,
378 /* [ */ 0,
379 /* \ */ 0,
380 /* ] */ 0,
381 /* ^ */ 0,
382 /* _ */ 0,
383 /* ` */ 0,
384 /* a */ C_ALPHA,
385 /* b */ C_ALPHA,
386 /* c */ C_ALPHA,
387 /* d */ C_ALPHA,
388 /* e */ C_ALPHA,
389 /* f */ C_ALPHA,
390 /* g */ C_ALPHA,
391 /* h */ C_ALPHA,
392 /* i */ C_ALPHA,
393 /* j */ C_ALPHA,
394 /* k */ C_ALPHA,
395 /* l */ C_ALPHA,
396 /* m */ C_ALPHA,
397 /* n */ C_ALPHA,
398 /* o */ C_ALPHA,
399 /* p */ C_ALPHA,
400 /* q */ C_ALPHA,
401 /* r */ C_ALPHA,
402 /* s */ C_ALPHA,
403 /* t */ C_ALPHA,
404 /* u */ C_ALPHA,
405 /* v */ C_ALPHA,
406 /* w */ C_ALPHA,
407 /* x */ C_ALPHA,
408 /* y */ C_ALPHA,
409 /* z */ C_ALPHA,
410 /* { */ 0,
411 /* | */ 0,
412 /* } */ 0,
413 /* ~ */ 0,
414 /* del */ 0,
415 /* 0x80 */ 0,
416 /* 0x81 */ 0,
417 /* 0x82 */ 0,
418 /* 0x83 */ 0,
419 /* 0x84 */ 0,
420 /* 0x85 */ 0,
421 /* 0x86 */ 0,
422 /* 0x87 */ 0,
423 /* 0x88 */ 0,
424 /* 0x89 */ 0,
425 /* 0x8A */ 0,
426 /* 0x8B */ 0,
427 /* 0x8C */ 0,
428 /* 0x8D */ 0,
429 /* 0x8E */ 0,
430 /* 0x8F */ 0,
431 /* 0x90 */ 0,
432 /* 0x91 */ 0,
433 /* 0x92 */ 0,
434 /* 0x93 */ 0,
435 /* 0x94 */ 0,
436 /* 0x95 */ 0,
437 /* 0x96 */ 0,
438 /* 0x97 */ 0,
439 /* 0x98 */ 0,
440 /* 0x99 */ 0,
441 /* 0x9A */ 0,
442 /* 0x9B */ 0,
443 /* 0x9C */ 0,
444 /* 0x9D */ 0,
445 /* 0x9E */ 0,
446 /* 0x9F */ 0,
447 /* 0xA0 */ 0,
448 /* 0xA1 */ 0,
449 /* 0xA2 */ 0,
450 /* 0xA3 */ 0,
451 /* 0xA4 */ 0,
452 /* 0xA5 */ 0,
453 /* 0xA6 */ 0,
454 /* 0xA7 */ 0,
455 /* 0xA8 */ 0,
456 /* 0xA9 */ 0,
457 /* 0xAA */ 0,
458 /* 0xAB */ 0,
459 /* 0xAC */ 0,
460 /* 0xAD */ 0,
461 /* 0xAE */ 0,
462 /* 0xAF */ 0,
463 /* 0xB0 */ 0,
464 /* 0xB1 */ 0,
465 /* 0xB2 */ 0,
466 /* 0xB3 */ 0,
467 /* 0xB4 */ 0,
468 /* 0xB5 */ 0,
469 /* 0xB6 */ 0,
470 /* 0xB7 */ 0,
471 /* 0xB8 */ 0,
472 /* 0xB9 */ 0,
473 /* 0xBA */ 0,
474 /* 0xBB */ 0,
475 /* 0xBC */ 0,
476 /* 0xBD */ 0,
477 /* 0xBE */ 0,
478 /* 0xBF */ 0,
479 /* 0xC0 */ 0,
480 /* 0xC1 */ 0,
481 /* 0xC2 */ 0,
482 /* 0xC3 */ 0,
483 /* 0xC4 */ 0,
484 /* 0xC5 */ 0,
485 /* 0xC6 */ 0,
486 /* 0xC7 */ 0,
487 /* 0xC8 */ 0,
488 /* 0xC9 */ 0,
489 /* 0xCA */ 0,
490 /* 0xCB */ 0,
491 /* 0xCC */ 0,
492 /* 0xCD */ 0,
493 /* 0xCE */ 0,
494 /* 0xCF */ 0,
495 /* 0xD0 */ 0,
496 /* 0xD1 */ 0,
497 /* 0xD2 */ 0,
498 /* 0xD3 */ 0,
499 /* 0xD4 */ 0,
500 /* 0xD5 */ 0,
501 /* 0xD6 */ 0,
502 /* 0xD7 */ 0,
503 /* 0xD8 */ 0,
504 /* 0xD9 */ 0,
505 /* 0xDA */ 0,
506 /* 0xDB */ 0,
507 /* 0xDC */ 0,
508 /* 0xDD */ 0,
509 /* 0xDE */ 0,
510 /* 0xDF */ 0,
511 /* 0xE0 */ 0,
512 /* 0xE1 */ 0,
513 /* 0xE2 */ 0,
514 /* 0xE3 */ 0,
515 /* 0xE4 */ 0,
516 /* 0xE5 */ 0,
517 /* 0xE6 */ 0,
518 /* 0xE7 */ 0,
519 /* 0xE8 */ 0,
520 /* 0xE9 */ 0,
521 /* 0xEA */ 0,
522 /* 0xEB */ 0,
523 /* 0xEC */ 0,
524 /* 0xED */ 0,
525 /* 0xEE */ 0,
526 /* 0xEF */ 0,
527 /* 0xF0 */ 0,
528 /* 0xF1 */ 0,
529 /* 0xF2 */ 0,
530 /* 0xF3 */ 0,
531 /* 0xF4 */ 0,
532 /* 0xF5 */ 0,
533 /* 0xF6 */ 0,
534 /* 0xF7 */ 0,
535 /* 0xF8 */ 0,
536 /* 0xF9 */ 0,
537 /* 0xFA */ 0,
538 /* 0xFB */ 0,
539 /* 0xFC */ 0,
540 /* 0xFD */ 0,
541 /* 0xFE */ 0,
542 /* 0xFF */ 0,
543 };
544
545 /*
546 * regex_compile()
547 * Compile a regex of `pattern' and return it.
548 */
549 regex_t *
550 regex_create (char *pattern, int flags)
551 {
552 static char errmsg[BUFSIZE];
553 int errnum;
554 regex_t *preg;
555
556 if (pattern == NULL)
557 {
558 return NULL;
559 }
560
561 preg = (regex_t *) malloc (sizeof (regex_t));
562 errnum = regcomp (preg, pattern, (flags & AREGEX_ICASE ? REG_ICASE : 0) | REG_EXTENDED);
563
564 if (errnum != 0)
565 {
566 regerror (errnum, preg, errmsg, BUFSIZE);
567 slog (LG_ERROR, "regex_match(): %s\n", errmsg);
568 regfree (preg);
569 free (preg);
570 return NULL;
571 }
572
573 return preg;
574 }
575
576 char *
577 regex_extract (char *pattern, char **pend, int *pflags)
578 {
579 char c, *p, *p2;
580 bool backslash = false;
581
582 c = *pattern;
583 if (isalnum (c) || isspace (c) || c == '\\')
584 return NULL;
585 p = pattern + 1;
586 while (*p != c || backslash)
587 {
588 if (*p == '\0')
589 return NULL;
590 if (backslash || *p == '\\')
591 backslash = !backslash;
592 p++;
593 }
594 p2 = p;
595 p++;
596 *pflags = 0;
597 while (*p != '\0' && *p != ' ')
598 {
599 if (*p == 'i')
600 *pflags |= AREGEX_ICASE;
601 else if (!isalnum (*p))
602 return NULL;
603 p++;
604 }
605 *pend = p;
606 *p2 = '\0';
607 return pattern + 1;
608 }
609
610 /*
611 * regex_match()
612 * Internal wrapper API for POSIX-based regex matching.
613 * `preg' is the regex to check with, `string' needs to be checked against.
614 * Returns `true' on match, `false' else.
615 */
616 bool
617 regex_match (regex_t * preg, char *string)
618 {
619 bool retval;
620
621 if (preg == NULL || string == NULL)
622 {
623 slog (LG_ERROR, "regex_match(): we were given NULL string or pattern, bad!");
624 return false;
625 }
626
627 /* match it */
628 if (regexec (preg, string, 0, NULL, 0) == 0)
629 retval = true;
630 else
631 retval = false;
632
633 return retval;
634 }
635
636 /*
637 * regex_destroy()
638 * Perform cleanup with regex `preg', free associated memory.
639 */
640 bool
641 regex_destroy (regex_t * preg)
642 {
643 regfree (preg);
644 free (preg);
645 return true;
646 }
647
648 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
649 * vim:ts=8
650 * vim:sw=8
651 * vim:noexpandtab
652 */