blob: ae441c5e4431ba3bc2608277f44a2f66b561797f [file] [log] [blame]
Emeric Vigier2f625822012-08-06 11:09:52 -04001/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2/* desktop-file.c .desktop file parser
3 *
4 * Copyright (C) 2003 CodeFactory AB
5 * Copyright (C) 2003 Red Hat Inc.
6 *
7 * Licensed under the Academic Free License version 2.1
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 *
23 */
24
25#include <config.h>
26#include <dbus/dbus-sysdeps.h>
27#include <dbus/dbus-internals.h>
28#include "desktop-file.h"
29#include "utils.h"
30
31typedef struct
32{
33 char *key;
34 char *value;
35} BusDesktopFileLine;
36
37typedef struct
38{
39 char *section_name;
40
41 int n_lines;
42 BusDesktopFileLine *lines;
43 int n_allocated_lines;
44} BusDesktopFileSection;
45
46struct BusDesktopFile
47{
48 int n_sections;
49 BusDesktopFileSection *sections;
50 int n_allocated_sections;
51};
52
53/**
54 * Parser for service files.
55 */
56typedef struct
57{
58 DBusString data; /**< The data from the file */
59
60 BusDesktopFile *desktop_file; /**< The resulting object */
61 int current_section; /**< The current section being parsed */
62
63 int pos; /**< Current position */
64 int len; /**< Length */
65 int line_num; /**< Current line number */
66
67} BusDesktopFileParser;
68
69#define VALID_KEY_CHAR 1
70#define VALID_LOCALE_CHAR 2
71static unsigned char valid[256] = {
72 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
73 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
74 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x2 , 0x0 ,
75 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
76 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 ,
77 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 ,
78 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 ,
79 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
80 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
81 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
82 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
83 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
84 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
85 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
86 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
87 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
88};
89
90static void report_error (BusDesktopFileParser *parser,
91 char *message,
92 const char *error_name,
93 DBusError *error);
94
95static void
96parser_free (BusDesktopFileParser *parser)
97{
98 bus_desktop_file_free (parser->desktop_file);
99
100 _dbus_string_free (&parser->data);
101}
102
103static void
104bus_desktop_file_line_free (BusDesktopFileLine *line)
105{
106 dbus_free (line->key);
107 dbus_free (line->value);
108}
109
110static void
111bus_desktop_file_section_free (BusDesktopFileSection *section)
112{
113 int i;
114
115 for (i = 0; i < section->n_lines; i++)
116 bus_desktop_file_line_free (&section->lines[i]);
117
118 dbus_free (section->lines);
119 dbus_free (section->section_name);
120}
121
122void
123bus_desktop_file_free (BusDesktopFile *desktop_file)
124{
125 int i;
126
127 for (i = 0; i < desktop_file->n_sections; i++)
128 bus_desktop_file_section_free (&desktop_file->sections[i]);
129 dbus_free (desktop_file->sections);
130
131 dbus_free (desktop_file);
132}
133
134static dbus_bool_t
135grow_lines_in_section (BusDesktopFileSection *section)
136{
137 BusDesktopFileLine *lines;
138
139 int new_n_lines;
140
141 if (section->n_allocated_lines == 0)
142 new_n_lines = 1;
143 else
144 new_n_lines = section->n_allocated_lines*2;
145
146 lines = dbus_realloc (section->lines,
147 sizeof (BusDesktopFileLine) * new_n_lines);
148
149 if (lines == NULL)
150 return FALSE;
151
152 section->lines = lines;
153 section->n_allocated_lines = new_n_lines;
154
155 return TRUE;
156}
157
158static dbus_bool_t
159grow_sections (BusDesktopFile *desktop_file)
160{
161 int new_n_sections;
162 BusDesktopFileSection *sections;
163
164 if (desktop_file->n_allocated_sections == 0)
165 new_n_sections = 1;
166 else
167 new_n_sections = desktop_file->n_allocated_sections*2;
168
169 sections = dbus_realloc (desktop_file->sections,
170 sizeof (BusDesktopFileSection) * new_n_sections);
171 if (sections == NULL)
172 return FALSE;
173
174 desktop_file->sections = sections;
175
176 desktop_file->n_allocated_sections = new_n_sections;
177
178 return TRUE;
179}
180
181static char *
182unescape_string (BusDesktopFileParser *parser,
183 const DBusString *str,
184 int pos,
185 int end_pos,
186 DBusError *error)
187{
188 char *retval, *q;
189
190 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
191
192 /* len + 1 is enough, because unescaping never makes the
193 * string longer
194 */
195 retval = dbus_malloc (end_pos - pos + 1);
196 if (retval == NULL)
197 {
198 BUS_SET_OOM (error);
199 return NULL;
200 }
201
202 q = retval;
203
204 while (pos < end_pos)
205 {
206 if (_dbus_string_get_byte (str, pos) == 0)
207 {
208 /* Found an embedded null */
209 dbus_free (retval);
210 report_error (parser, "Text to be unescaped contains embedded nul",
211 BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error);
212 return NULL;
213 }
214
215 if (_dbus_string_get_byte (str, pos) == '\\')
216 {
217 pos ++;
218
219 if (pos >= end_pos)
220 {
221 /* Escape at end of string */
222 dbus_free (retval);
223 report_error (parser, "Text to be unescaped ended in \\",
224 BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error);
225 return NULL;
226 }
227
228 switch (_dbus_string_get_byte (str, pos))
229 {
230 case 's':
231 *q++ = ' ';
232 break;
233 case 't':
234 *q++ = '\t';
235 break;
236 case 'n':
237 *q++ = '\n';
238 break;
239 case 'r':
240 *q++ = '\r';
241 break;
242 case '\\':
243 *q++ = '\\';
244 break;
245 default:
246 /* Invalid escape code */
247 dbus_free (retval);
248 report_error (parser, "Text to be unescaped had invalid escape sequence",
249 BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error);
250 return NULL;
251 }
252 pos++;
253 }
254 else
255 {
256 *q++ =_dbus_string_get_byte (str, pos);
257
258 pos++;
259 }
260 }
261
262 *q = 0;
263
264 return retval;
265}
266
267static BusDesktopFileSection*
268new_section (BusDesktopFile *desktop_file,
269 const char *name)
270{
271 int n;
272 char *name_copy;
273
274 if (desktop_file->n_allocated_sections == desktop_file->n_sections)
275 {
276 if (!grow_sections (desktop_file))
277 return NULL;
278 }
279
280 name_copy = _dbus_strdup (name);
281 if (name_copy == NULL)
282 return NULL;
283
284 n = desktop_file->n_sections;
285 desktop_file->sections[n].section_name = name_copy;
286
287 desktop_file->sections[n].n_lines = 0;
288 desktop_file->sections[n].lines = NULL;
289 desktop_file->sections[n].n_allocated_lines = 0;
290
291 if (!grow_lines_in_section (&desktop_file->sections[n]))
292 {
293 dbus_free (desktop_file->sections[n].section_name);
294 desktop_file->sections[n].section_name = NULL;
295 return NULL;
296 }
297
298 desktop_file->n_sections += 1;
299
300 return &desktop_file->sections[n];
301}
302
303static BusDesktopFileSection*
304open_section (BusDesktopFileParser *parser,
305 char *name)
306{
307 BusDesktopFileSection *section;
308
309 section = new_section (parser->desktop_file, name);
310 if (section == NULL)
311 return NULL;
312
313 parser->current_section = parser->desktop_file->n_sections - 1;
314 _dbus_assert (&parser->desktop_file->sections[parser->current_section] == section);
315
316 return section;
317}
318
319static BusDesktopFileLine *
320new_line (BusDesktopFileParser *parser)
321{
322 BusDesktopFileSection *section;
323 BusDesktopFileLine *line;
324
325 section = &parser->desktop_file->sections[parser->current_section];
326
327 if (section->n_allocated_lines == section->n_lines)
328 {
329 if (!grow_lines_in_section (section))
330 return NULL;
331 }
332
333 line = &section->lines[section->n_lines++];
334
335 _DBUS_ZERO(*line);
336
337 return line;
338}
339
340static dbus_bool_t
341is_blank_line (BusDesktopFileParser *parser)
342{
343 int p;
344 char c;
345
346 p = parser->pos;
347
348 c = _dbus_string_get_byte (&parser->data, p);
349
350 while (c && c != '\n')
351 {
352 if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'))
353 return FALSE;
354
355 p++;
356 c = _dbus_string_get_byte (&parser->data, p);
357 }
358
359 return TRUE;
360}
361
362static void
363parse_comment_or_blank (BusDesktopFileParser *parser)
364{
365 int line_end, eol_len;
366
367 if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len))
368 line_end = parser->len;
369
370 if (line_end == parser->len)
371 parser->pos = parser->len;
372 else
373 parser->pos = line_end + eol_len;
374
375 parser->line_num += 1;
376}
377
378static dbus_bool_t
379is_valid_section_name (const char *name)
380{
381 /* 5. Group names may contain all ASCII characters except for control characters and '[' and ']'. */
382
383 while (*name)
384 {
385 if (!((*name >= 'A' && *name <= 'Z') || (*name >= 'a' || *name <= 'z') ||
386 *name == '\n' || *name == '\t'))
387 return FALSE;
388
389 name++;
390 }
391
392 return TRUE;
393}
394
395static dbus_bool_t
396parse_section_start (BusDesktopFileParser *parser, DBusError *error)
397{
398 int line_end, eol_len;
399 char *section_name;
400
401 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
402
403 if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len))
404 line_end = parser->len;
405
406 if (line_end - parser->pos <= 2 ||
407 _dbus_string_get_byte (&parser->data, line_end - 1) != ']')
408 {
409 report_error (parser, "Invalid syntax for section header", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error);
410 parser_free (parser);
411 return FALSE;
412 }
413
414 section_name = unescape_string (parser,
415 &parser->data, parser->pos + 1, line_end - 1,
416 error);
417
418 if (section_name == NULL)
419 {
420 parser_free (parser);
421 return FALSE;
422 }
423
424 if (!is_valid_section_name (section_name))
425 {
426 report_error (parser, "Invalid characters in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error);
427 parser_free (parser);
428 dbus_free (section_name);
429 return FALSE;
430 }
431
432 if (open_section (parser, section_name) == NULL)
433 {
434 dbus_free (section_name);
435 parser_free (parser);
436 BUS_SET_OOM (error);
437 return FALSE;
438 }
439
440 if (line_end == parser->len)
441 parser->pos = parser->len;
442 else
443 parser->pos = line_end + eol_len;
444
445 parser->line_num += 1;
446
447 dbus_free (section_name);
448
449 return TRUE;
450}
451
452static dbus_bool_t
453parse_key_value (BusDesktopFileParser *parser, DBusError *error)
454{
455 int line_end, eol_len;
456 int key_start, key_end;
457 int value_start;
458 int p;
459 char *value, *tmp;
460 DBusString key;
461 BusDesktopFileLine *line;
462
463 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
464
465 if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len))
466 line_end = parser->len;
467
468 p = parser->pos;
469 key_start = p;
470 while (p < line_end &&
471 (valid[_dbus_string_get_byte (&parser->data, p)] & VALID_KEY_CHAR))
472 p++;
473 key_end = p;
474
475 if (key_start == key_end)
476 {
477 report_error (parser, "Empty key name", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error);
478 parser_free (parser);
479 return FALSE;
480 }
481
482 /* We ignore locales for now */
483 if (p < line_end && _dbus_string_get_byte (&parser->data, p) == '[')
484 {
485 if (line_end == parser->len)
486 parser->pos = parser->len;
487 else
488 parser->pos = line_end + eol_len;
489
490 parser->line_num += 1;
491
492 return TRUE;
493 }
494
495 /* Skip space before '=' */
496 while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
497 p++;
498
499 if (p < line_end && _dbus_string_get_byte (&parser->data, p) != '=')
500 {
501 report_error (parser, "Invalid characters in key name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error);
502 parser_free (parser);
503 return FALSE;
504 }
505
506 if (p == line_end)
507 {
508 report_error (parser, "No '=' in key/value pair", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error);
509 parser_free (parser);
510 return FALSE;
511 }
512
513 /* Skip the '=' */
514 p++;
515
516 /* Skip space after '=' */
517 while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
518 p++;
519
520 value_start = p;
521
522 value = unescape_string (parser, &parser->data, value_start, line_end, error);
523 if (value == NULL)
524 {
525 parser_free (parser);
526 return FALSE;
527 }
528
529 line = new_line (parser);
530 if (line == NULL)
531 {
532 dbus_free (value);
533 parser_free (parser);
534 BUS_SET_OOM (error);
535 return FALSE;
536 }
537
538 if (!_dbus_string_init (&key))
539 {
540 dbus_free (value);
541 parser_free (parser);
542 BUS_SET_OOM (error);
543 return FALSE;
544 }
545
546 if (!_dbus_string_copy_len (&parser->data, key_start, key_end - key_start,
547 &key, 0))
548 {
549 _dbus_string_free (&key);
550 dbus_free (value);
551 parser_free (parser);
552 BUS_SET_OOM (error);
553 return FALSE;
554 }
555
556 if (!_dbus_string_steal_data (&key, &tmp))
557 {
558 _dbus_string_free (&key);
559 dbus_free (value);
560 parser_free (parser);
561 BUS_SET_OOM (error);
562 return FALSE;
563 }
564
565 _dbus_string_free (&key);
566
567 line->key = tmp;
568 line->value = value;
569
570 if (line_end == parser->len)
571 parser->pos = parser->len;
572 else
573 parser->pos = line_end + eol_len;
574
575 parser->line_num += 1;
576
577 return TRUE;
578}
579
580static void
581report_error (BusDesktopFileParser *parser,
582 char *message,
583 const char *error_name,
584 DBusError *error)
585{
586 const char *section_name = NULL;
587
588 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
589
590 if (parser->current_section != -1)
591 section_name = parser->desktop_file->sections[parser->current_section].section_name;
592
593 if (section_name)
594 dbus_set_error (error, error_name,
595 "Error in section %s at line %d: %s\n", section_name, parser->line_num, message);
596 else
597 dbus_set_error (error, error_name,
598 "Error at line %d: %s\n", parser->line_num, message);
599}
600
601#if 0
602static void
603dump_desktop_file (BusDesktopFile *file)
604{
605 int i;
606
607 for (i = 0; i < file->n_sections; i++)
608 {
609 int j;
610
611 printf ("[%s]\n", file->sections[i].section_name);
612
613 for (j = 0; j < file->sections[i].n_lines; j++)
614 {
615 printf ("%s=%s\n", file->sections[i].lines[j].key,
616 file->sections[i].lines[j].value);
617 }
618 }
619}
620#endif
621
622BusDesktopFile*
623bus_desktop_file_load (DBusString *filename,
624 DBusError *error)
625{
626 DBusString str;
627 BusDesktopFileParser parser;
628 DBusStat sb;
629
630 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
631
632 /* Clearly there's a race here, but it's just to make it unlikely
633 * that we do something silly, we still handle doing it below.
634 */
635 if (!_dbus_stat (filename, &sb, error))
636 return NULL;
637
638 if (sb.size > _DBUS_ONE_KILOBYTE * 128)
639 {
640 dbus_set_error (error, DBUS_ERROR_FAILED,
641 "Desktop file size (%ld bytes) is too large", (long) sb.size);
642 return NULL;
643 }
644
645 if (!_dbus_string_init (&str))
646 {
647 BUS_SET_OOM (error);
648 return NULL;
649 }
650
651 if (!_dbus_file_get_contents (&str, filename, error))
652 {
653 _dbus_string_free (&str);
654 return NULL;
655 }
656
657 if (!_dbus_string_validate_utf8 (&str, 0, _dbus_string_get_length (&str)))
658 {
659 _dbus_string_free (&str);
660 dbus_set_error (error, DBUS_ERROR_FAILED,
661 "invalid UTF-8");
662 return NULL;
663 }
664
665 parser.desktop_file = dbus_new0 (BusDesktopFile, 1);
666 if (parser.desktop_file == NULL)
667 {
668 _dbus_string_free (&str);
669 BUS_SET_OOM (error);
670 return NULL;
671 }
672
673 parser.data = str;
674 parser.line_num = 1;
675 parser.pos = 0;
676 parser.len = _dbus_string_get_length (&parser.data);
677 parser.current_section = -1;
678
679 while (parser.pos < parser.len)
680 {
681 if (_dbus_string_get_byte (&parser.data, parser.pos) == '[')
682 {
683 if (!parse_section_start (&parser, error))
684 {
685 return NULL;
686 }
687 }
688 else if (is_blank_line (&parser) ||
689 _dbus_string_get_byte (&parser.data, parser.pos) == '#')
690 parse_comment_or_blank (&parser);
691 else
692 {
693 if (!parse_key_value (&parser, error))
694 {
695 return NULL;
696 }
697 }
698 }
699
700 _dbus_string_free (&parser.data);
701
702 return parser.desktop_file;
703}
704
705static BusDesktopFileSection *
706lookup_section (BusDesktopFile *desktop_file,
707 const char *section_name)
708{
709 BusDesktopFileSection *section;
710 int i;
711
712 if (section_name == NULL)
713 return NULL;
714
715 for (i = 0; i < desktop_file->n_sections; i ++)
716 {
717 section = &desktop_file->sections[i];
718
719 if (strcmp (section->section_name, section_name) == 0)
720 return section;
721 }
722
723 return NULL;
724}
725
726static BusDesktopFileLine *
727lookup_line (BusDesktopFile *desktop_file,
728 BusDesktopFileSection *section,
729 const char *keyname)
730{
731 BusDesktopFileLine *line;
732 int i;
733
734 for (i = 0; i < section->n_lines; i++)
735 {
736 line = &section->lines[i];
737
738 if (strcmp (line->key, keyname) == 0)
739 return line;
740 }
741
742 return NULL;
743}
744
745dbus_bool_t
746bus_desktop_file_get_raw (BusDesktopFile *desktop_file,
747 const char *section_name,
748 const char *keyname,
749 const char **val)
750{
751 BusDesktopFileSection *section;
752 BusDesktopFileLine *line;
753
754 *val = NULL;
755
756 section = lookup_section (desktop_file, section_name);
757
758 if (!section)
759 return FALSE;
760
761 line = lookup_line (desktop_file,
762 section,
763 keyname);
764
765 if (!line)
766 return FALSE;
767
768 *val = line->value;
769
770 return TRUE;
771}
772
773dbus_bool_t
774bus_desktop_file_get_string (BusDesktopFile *desktop_file,
775 const char *section,
776 const char *keyname,
777 char **val,
778 DBusError *error)
779{
780 const char *raw;
781
782 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
783
784 *val = NULL;
785
786 if (!bus_desktop_file_get_raw (desktop_file, section, keyname, &raw))
787 {
788 dbus_set_error (error, DBUS_ERROR_FAILED,
789 "No \"%s\" key in .service file\n", keyname);
790 return FALSE;
791 }
792
793 *val = _dbus_strdup (raw);
794
795 if (*val == NULL)
796 {
797 BUS_SET_OOM (error);
798 return FALSE;
799 }
800
801 return TRUE;
802}