The real, definitive, Visual C++ support branch. Accept no substitutes
[reactos.git] / lib / inflib / infcore.c
1 /*
2 * PROJECT: .inf file parser
3 * LICENSE: GPL - See COPYING in the top level directory
4 * PROGRAMMER: Royce Mitchell III
5 * Eric Kohl
6 * Ge van Geldorp <gvg@reactos.org>
7 */
8
9 /* INCLUDES *****************************************************************/
10
11 #include "inflib.h"
12
13 #define NDEBUG
14 #include <debug.h>
15
16 #define CONTROL_Z '\x1a'
17 #define MAX_SECTION_NAME_LEN 255
18 #define MAX_FIELD_LEN 511 /* larger fields get silently truncated */
19 /* actual string limit is MAX_INF_STRING_LENGTH+1 (plus terminating null) under Windows */
20 #define MAX_STRING_LEN (MAX_INF_STRING_LENGTH+1)
21
22
23 /* parser definitions */
24
25 enum parser_state
26 {
27 LINE_START, /* at beginning of a line */
28 SECTION_NAME, /* parsing a section name */
29 KEY_NAME, /* parsing a key name */
30 VALUE_NAME, /* parsing a value name */
31 EOL_BACKSLASH, /* backslash at end of line */
32 QUOTES, /* inside quotes */
33 LEADING_SPACES, /* leading spaces */
34 TRAILING_SPACES, /* trailing spaces */
35 COMMENT, /* inside a comment */
36 NB_PARSER_STATES
37 };
38
39 struct parser
40 {
41 const CHAR *start; /* start position of item being parsed */
42 const CHAR *end; /* end of buffer */
43 PINFCACHE file; /* file being built */
44 enum parser_state state; /* current parser state */
45 enum parser_state stack[4]; /* state stack */
46 int stack_pos; /* current pos in stack */
47
48 PINFCACHESECTION cur_section; /* pointer to the section being parsed*/
49 PINFCACHELINE line; /* current line */
50 unsigned int line_pos; /* current line position in file */
51 unsigned int error; /* error code */
52 unsigned int token_len; /* current token len */
53 TCHAR token[MAX_FIELD_LEN+1]; /* current token */
54 };
55
56 typedef const CHAR * (*parser_state_func)( struct parser *parser, const CHAR *pos );
57
58 /* parser state machine functions */
59 static const CHAR *line_start_state( struct parser *parser, const CHAR *pos );
60 static const CHAR *section_name_state( struct parser *parser, const CHAR *pos );
61 static const CHAR *key_name_state( struct parser *parser, const CHAR *pos );
62 static const CHAR *value_name_state( struct parser *parser, const CHAR *pos );
63 static const CHAR *eol_backslash_state( struct parser *parser, const CHAR *pos );
64 static const CHAR *quotes_state( struct parser *parser, const CHAR *pos );
65 static const CHAR *leading_spaces_state( struct parser *parser, const CHAR *pos );
66 static const CHAR *trailing_spaces_state( struct parser *parser, const CHAR *pos );
67 static const CHAR *comment_state( struct parser *parser, const CHAR *pos );
68
69 static const parser_state_func parser_funcs[NB_PARSER_STATES] =
70 {
71 line_start_state, /* LINE_START */
72 section_name_state, /* SECTION_NAME */
73 key_name_state, /* KEY_NAME */
74 value_name_state, /* VALUE_NAME */
75 eol_backslash_state, /* EOL_BACKSLASH */
76 quotes_state, /* QUOTES */
77 leading_spaces_state, /* LEADING_SPACES */
78 trailing_spaces_state, /* TRAILING_SPACES */
79 comment_state /* COMMENT */
80 };
81
82
83 /* PRIVATE FUNCTIONS ********************************************************/
84
85 static PINFCACHELINE
86 InfpFreeLine (PINFCACHELINE Line)
87 {
88 PINFCACHELINE Next;
89 PINFCACHEFIELD Field;
90
91 if (Line == NULL)
92 {
93 return NULL;
94 }
95
96 Next = Line->Next;
97 if (Line->Key != NULL)
98 {
99 FREE (Line->Key);
100 Line->Key = NULL;
101 }
102
103 /* Remove data fields */
104 while (Line->FirstField != NULL)
105 {
106 Field = Line->FirstField->Next;
107 FREE (Line->FirstField);
108 Line->FirstField = Field;
109 }
110 Line->LastField = NULL;
111
112 FREE (Line);
113
114 return Next;
115 }
116
117
118 PINFCACHESECTION
119 InfpFreeSection (PINFCACHESECTION Section)
120 {
121 PINFCACHESECTION Next;
122
123 if (Section == NULL)
124 {
125 return NULL;
126 }
127
128 /* Release all keys */
129 Next = Section->Next;
130 while (Section->FirstLine != NULL)
131 {
132 Section->FirstLine = InfpFreeLine (Section->FirstLine);
133 }
134 Section->LastLine = NULL;
135
136 FREE (Section);
137
138 return Next;
139 }
140
141
142 PINFCACHESECTION
143 InfpFindSection(PINFCACHE Cache,
144 PCTSTR Name)
145 {
146 PINFCACHESECTION Section = NULL;
147
148 if (Cache == NULL || Name == NULL)
149 {
150 return NULL;
151 }
152
153 /* iterate through list of sections */
154 Section = Cache->FirstSection;
155 while (Section != NULL)
156 {
157 if (_tcsicmp (Section->Name, Name) == 0)
158 {
159 return Section;
160 }
161
162 /* get the next section*/
163 Section = Section->Next;
164 }
165
166 return NULL;
167 }
168
169
170 PINFCACHESECTION
171 InfpAddSection(PINFCACHE Cache,
172 PCTSTR Name)
173 {
174 PINFCACHESECTION Section = NULL;
175 ULONG Size;
176
177 if (Cache == NULL || Name == NULL)
178 {
179 DPRINT("Invalid parameter\n");
180 return NULL;
181 }
182
183 /* Allocate and initialize the new section */
184 Size = FIELD_OFFSET(INFCACHESECTION,
185 Name[_tcslen (Name) + 1]);
186 Section = (PINFCACHESECTION)MALLOC (Size);
187 if (Section == NULL)
188 {
189 DPRINT("MALLOC() failed\n");
190 return NULL;
191 }
192 ZEROMEMORY (Section,
193 Size);
194
195 /* Copy section name */
196 _tcscpy (Section->Name, Name);
197
198 /* Append section */
199 if (Cache->FirstSection == NULL)
200 {
201 Cache->FirstSection = Section;
202 Cache->LastSection = Section;
203 }
204 else
205 {
206 Cache->LastSection->Next = Section;
207 Section->Prev = Cache->LastSection;
208 Cache->LastSection = Section;
209 }
210
211 return Section;
212 }
213
214
215 PINFCACHELINE
216 InfpAddLine(PINFCACHESECTION Section)
217 {
218 PINFCACHELINE Line;
219
220 if (Section == NULL)
221 {
222 DPRINT("Invalid parameter\n");
223 return NULL;
224 }
225
226 Line = (PINFCACHELINE)MALLOC (sizeof(INFCACHELINE));
227 if (Line == NULL)
228 {
229 DPRINT("MALLOC() failed\n");
230 return NULL;
231 }
232 ZEROMEMORY(Line,
233 sizeof(INFCACHELINE));
234
235 /* Append line */
236 if (Section->FirstLine == NULL)
237 {
238 Section->FirstLine = Line;
239 Section->LastLine = Line;
240 }
241 else
242 {
243 Section->LastLine->Next = Line;
244 Line->Prev = Section->LastLine;
245 Section->LastLine = Line;
246 }
247 Section->LineCount++;
248
249 return Line;
250 }
251
252
253 PVOID
254 InfpAddKeyToLine(PINFCACHELINE Line,
255 PCTSTR Key)
256 {
257 if (Line == NULL)
258 {
259 DPRINT1("Invalid Line\n");
260 return NULL;
261 }
262
263 if (Line->Key != NULL)
264 {
265 DPRINT1("Line already has a key\n");
266 return NULL;
267 }
268
269 Line->Key = (PTCHAR)MALLOC((_tcslen(Key) + 1) * sizeof(TCHAR));
270 if (Line->Key == NULL)
271 {
272 DPRINT1("MALLOC() failed\n");
273 return NULL;
274 }
275
276 _tcscpy(Line->Key, Key);
277
278 return (PVOID)Line->Key;
279 }
280
281
282 PVOID
283 InfpAddFieldToLine(PINFCACHELINE Line,
284 PCTSTR Data)
285 {
286 PINFCACHEFIELD Field;
287 ULONG Size;
288
289 Size = FIELD_OFFSET(INFCACHEFIELD,
290 Data[_tcslen(Data) + 1]);
291 Field = (PINFCACHEFIELD)MALLOC(Size);
292 if (Field == NULL)
293 {
294 DPRINT1("MALLOC() failed\n");
295 return NULL;
296 }
297 ZEROMEMORY (Field,
298 Size);
299 _tcscpy (Field->Data, Data);
300
301 /* Append key */
302 if (Line->FirstField == NULL)
303 {
304 Line->FirstField = Field;
305 Line->LastField = Field;
306 }
307 else
308 {
309 Line->LastField->Next = Field;
310 Field->Prev = Line->LastField;
311 Line->LastField = Field;
312 }
313 Line->FieldCount++;
314
315 return (PVOID)Field;
316 }
317
318
319 PINFCACHELINE
320 InfpFindKeyLine(PINFCACHESECTION Section,
321 PCTSTR Key)
322 {
323 PINFCACHELINE Line;
324
325 Line = Section->FirstLine;
326 while (Line != NULL)
327 {
328 if (Line->Key != NULL && _tcsicmp (Line->Key, Key) == 0)
329 {
330 return Line;
331 }
332
333 Line = Line->Next;
334 }
335
336 return NULL;
337 }
338
339
340 /* push the current state on the parser stack */
341 __inline static void push_state( struct parser *parser, enum parser_state state )
342 {
343 // assert( parser->stack_pos < sizeof(parser->stack)/sizeof(parser->stack[0]) );
344 parser->stack[parser->stack_pos++] = state;
345 }
346
347
348 /* pop the current state */
349 __inline static void pop_state( struct parser *parser )
350 {
351 // assert( parser->stack_pos );
352 parser->state = parser->stack[--parser->stack_pos];
353 }
354
355
356 /* set the parser state and return the previous one */
357 __inline static enum parser_state set_state( struct parser *parser, enum parser_state state )
358 {
359 enum parser_state ret = parser->state;
360 parser->state = state;
361 return ret;
362 }
363
364
365 /* check if the pointer points to an end of file */
366 __inline static int is_eof( struct parser *parser, const CHAR *ptr )
367 {
368 return (ptr >= parser->end || *ptr == CONTROL_Z);
369 }
370
371
372 /* check if the pointer points to an end of line */
373 __inline static int is_eol( struct parser *parser, const CHAR *ptr )
374 {
375 return (ptr >= parser->end ||
376 *ptr == CONTROL_Z ||
377 *ptr == '\n' ||
378 (*ptr == '\r' && *(ptr + 1) == '\n'));
379 }
380
381
382 /* push data from current token start up to pos into the current token */
383 static int push_token( struct parser *parser, const CHAR *pos )
384 {
385 unsigned int len = pos - parser->start;
386 const CHAR *src = parser->start;
387 TCHAR *dst = parser->token + parser->token_len;
388
389 if (len > MAX_FIELD_LEN - parser->token_len)
390 len = MAX_FIELD_LEN - parser->token_len;
391
392 parser->token_len += len;
393 for ( ; len > 0; len--, dst++, src++)
394 *dst = *src ? (TCHAR)*src : L' ';
395 *dst = 0;
396 parser->start = pos;
397
398 return 0;
399 }
400
401
402
403 /* add a section with the current token as name */
404 static PVOID add_section_from_token( struct parser *parser )
405 {
406 PINFCACHESECTION Section;
407
408 if (parser->token_len > MAX_SECTION_NAME_LEN)
409 {
410 parser->error = INF_STATUS_SECTION_NAME_TOO_LONG;
411 return NULL;
412 }
413
414 Section = InfpFindSection(parser->file,
415 parser->token);
416 if (Section == NULL)
417 {
418 /* need to create a new one */
419 Section= InfpAddSection(parser->file,
420 parser->token);
421 if (Section == NULL)
422 {
423 parser->error = INF_STATUS_NOT_ENOUGH_MEMORY;
424 return NULL;
425 }
426 }
427
428 parser->token_len = 0;
429 parser->cur_section = Section;
430
431 return (PVOID)Section;
432 }
433
434
435 /* add a field containing the current token to the current line */
436 static struct field *add_field_from_token( struct parser *parser, int is_key )
437 {
438 PVOID field;
439
440 if (!parser->line) /* need to start a new line */
441 {
442 if (parser->cur_section == NULL) /* got a line before the first section */
443 {
444 parser->error = INF_STATUS_WRONG_INF_STYLE;
445 return NULL;
446 }
447
448 parser->line = InfpAddLine(parser->cur_section);
449 if (parser->line == NULL)
450 goto error;
451 }
452 else
453 {
454 // assert(!is_key);
455 }
456
457 if (is_key)
458 {
459 field = InfpAddKeyToLine(parser->line, parser->token);
460 }
461 else
462 {
463 field = InfpAddFieldToLine(parser->line, parser->token);
464 }
465
466 if (field != NULL)
467 {
468 parser->token_len = 0;
469 return field;
470 }
471
472 error:
473 parser->error = INF_STATUS_NOT_ENOUGH_MEMORY;
474 return NULL;
475 }
476
477
478 /* close the current line and prepare for parsing a new one */
479 static void close_current_line( struct parser *parser )
480 {
481 parser->line = NULL;
482 }
483
484
485
486 /* handler for parser LINE_START state */
487 static const CHAR *line_start_state( struct parser *parser, const CHAR *pos )
488 {
489 const CHAR *p;
490
491 for (p = pos; !is_eof( parser, p ); p++)
492 {
493 switch(*p)
494 {
495 case '\r':
496 continue;
497
498 case '\n':
499 parser->line_pos++;
500 close_current_line( parser );
501 break;
502
503 case ';':
504 push_state( parser, LINE_START );
505 set_state( parser, COMMENT );
506 return p + 1;
507
508 case '[':
509 parser->start = p + 1;
510 set_state( parser, SECTION_NAME );
511 return p + 1;
512
513 default:
514 if (!isspace(*p))
515 {
516 parser->start = p;
517 set_state( parser, KEY_NAME );
518 return p;
519 }
520 break;
521 }
522 }
523 close_current_line( parser );
524 return NULL;
525 }
526
527
528 /* handler for parser SECTION_NAME state */
529 static const CHAR *section_name_state( struct parser *parser, const CHAR *pos )
530 {
531 const CHAR *p;
532
533 for (p = pos; !is_eol( parser, p ); p++)
534 {
535 if (*p == ']')
536 {
537 push_token( parser, p );
538 if (add_section_from_token( parser ) == NULL)
539 return NULL;
540 push_state( parser, LINE_START );
541 set_state( parser, COMMENT ); /* ignore everything else on the line */
542 return p + 1;
543 }
544 }
545 parser->error = INF_STATUS_BAD_SECTION_NAME_LINE; /* unfinished section name */
546 return NULL;
547 }
548
549
550 /* handler for parser KEY_NAME state */
551 static const CHAR *key_name_state( struct parser *parser, const CHAR *pos )
552 {
553 const CHAR *p, *token_end = parser->start;
554
555 for (p = pos; !is_eol( parser, p ); p++)
556 {
557 if (*p == ',') break;
558 switch(*p)
559 {
560
561 case '=':
562 push_token( parser, token_end );
563 if (!add_field_from_token( parser, 1 )) return NULL;
564 parser->start = p + 1;
565 push_state( parser, VALUE_NAME );
566 set_state( parser, LEADING_SPACES );
567 return p + 1;
568 case ';':
569 push_token( parser, token_end );
570 if (!add_field_from_token( parser, 0 )) return NULL;
571 push_state( parser, LINE_START );
572 set_state( parser, COMMENT );
573 return p + 1;
574 case '"':
575 push_token( parser, token_end );
576 parser->start = p + 1;
577 push_state( parser, KEY_NAME );
578 set_state( parser, QUOTES );
579 return p + 1;
580 case '\\':
581 push_token( parser, token_end );
582 parser->start = p;
583 push_state( parser, KEY_NAME );
584 set_state( parser, EOL_BACKSLASH );
585 return p;
586 default:
587 if (!isspace(*p)) token_end = p + 1;
588 else
589 {
590 push_token( parser, p );
591 push_state( parser, KEY_NAME );
592 set_state( parser, TRAILING_SPACES );
593 return p;
594 }
595 break;
596 }
597 }
598 push_token( parser, token_end );
599 set_state( parser, VALUE_NAME );
600 return p;
601 }
602
603
604 /* handler for parser VALUE_NAME state */
605 static const CHAR *value_name_state( struct parser *parser, const CHAR *pos )
606 {
607 const CHAR *p, *token_end = parser->start;
608
609 for (p = pos; !is_eol( parser, p ); p++)
610 {
611 switch(*p)
612 {
613 case ';':
614 push_token( parser, token_end );
615 if (!add_field_from_token( parser, 0 )) return NULL;
616 push_state( parser, LINE_START );
617 set_state( parser, COMMENT );
618 return p + 1;
619 case ',':
620 push_token( parser, token_end );
621 if (!add_field_from_token( parser, 0 )) return NULL;
622 parser->start = p + 1;
623 push_state( parser, VALUE_NAME );
624 set_state( parser, LEADING_SPACES );
625 return p + 1;
626 case '"':
627 push_token( parser, token_end );
628 parser->start = p + 1;
629 push_state( parser, VALUE_NAME );
630 set_state( parser, QUOTES );
631 return p + 1;
632 case '\\':
633 push_token( parser, token_end );
634 parser->start = p;
635 push_state( parser, VALUE_NAME );
636 set_state( parser, EOL_BACKSLASH );
637 return p;
638 default:
639 if (!isspace(*p)) token_end = p + 1;
640 else
641 {
642 push_token( parser, p );
643 push_state( parser, VALUE_NAME );
644 set_state( parser, TRAILING_SPACES );
645 return p;
646 }
647 break;
648 }
649 }
650 push_token( parser, token_end );
651 if (!add_field_from_token( parser, 0 )) return NULL;
652 set_state( parser, LINE_START );
653 return p;
654 }
655
656
657 /* handler for parser EOL_BACKSLASH state */
658 static const CHAR *eol_backslash_state( struct parser *parser, const CHAR *pos )
659 {
660 const CHAR *p;
661
662 for (p = pos; !is_eof( parser, p ); p++)
663 {
664 switch(*p)
665 {
666 case '\r':
667 continue;
668
669 case '\n':
670 parser->line_pos++;
671 parser->start = p + 1;
672 set_state( parser, LEADING_SPACES );
673 return p + 1;
674
675 case '\\':
676 continue;
677
678 case ';':
679 push_state( parser, EOL_BACKSLASH );
680 set_state( parser, COMMENT );
681 return p + 1;
682
683 default:
684 if (isspace(*p))
685 continue;
686 push_token( parser, p );
687 pop_state( parser );
688 return p;
689 }
690 }
691 parser->start = p;
692 pop_state( parser );
693
694 return p;
695 }
696
697
698 /* handler for parser QUOTES state */
699 static const CHAR *quotes_state( struct parser *parser, const CHAR *pos )
700 {
701 const CHAR *p, *token_end = parser->start;
702
703 for (p = pos; !is_eol( parser, p ); p++)
704 {
705 if (*p == '"')
706 {
707 if (p+1 < parser->end && p[1] == '"') /* double quotes */
708 {
709 push_token( parser, p + 1 );
710 parser->start = token_end = p + 2;
711 p++;
712 }
713 else /* end of quotes */
714 {
715 push_token( parser, p );
716 parser->start = p + 1;
717 pop_state( parser );
718 return p + 1;
719 }
720 }
721 }
722 push_token( parser, p );
723 pop_state( parser );
724 return p;
725 }
726
727
728 /* handler for parser LEADING_SPACES state */
729 static const CHAR *leading_spaces_state( struct parser *parser, const CHAR *pos )
730 {
731 const CHAR *p;
732
733 for (p = pos; !is_eol( parser, p ); p++)
734 {
735 if (*p == '\\')
736 {
737 parser->start = p;
738 set_state( parser, EOL_BACKSLASH );
739 return p;
740 }
741 if (!isspace(*p))
742 break;
743 }
744 parser->start = p;
745 pop_state( parser );
746 return p;
747 }
748
749
750 /* handler for parser TRAILING_SPACES state */
751 static const CHAR *trailing_spaces_state( struct parser *parser, const CHAR *pos )
752 {
753 const CHAR *p;
754
755 for (p = pos; !is_eol( parser, p ); p++)
756 {
757 if (*p == '\\')
758 {
759 set_state( parser, EOL_BACKSLASH );
760 return p;
761 }
762 if (!isspace(*p))
763 break;
764 }
765 pop_state( parser );
766 return p;
767 }
768
769
770 /* handler for parser COMMENT state */
771 static const CHAR *comment_state( struct parser *parser, const CHAR *pos )
772 {
773 const CHAR *p = pos;
774
775 while (!is_eol( parser, p ))
776 p++;
777 pop_state( parser );
778 return p;
779 }
780
781
782 /* parse a complete buffer */
783 INFSTATUS
784 InfpParseBuffer (PINFCACHE file,
785 const CHAR *buffer,
786 const CHAR *end,
787 PULONG error_line)
788 {
789 struct parser parser;
790 const CHAR *pos = buffer;
791
792 parser.start = buffer;
793 parser.end = end;
794 parser.file = file;
795 parser.line = NULL;
796 parser.state = LINE_START;
797 parser.stack_pos = 0;
798 parser.cur_section = NULL;
799 parser.line_pos = 1;
800 parser.error = 0;
801 parser.token_len = 0;
802
803 /* parser main loop */
804 while (pos)
805 pos = (parser_funcs[parser.state])(&parser, pos);
806
807 if (parser.error)
808 {
809 if (error_line)
810 *error_line = parser.line_pos;
811 return parser.error;
812 }
813
814 /* find the [strings] section */
815 file->StringsSection = InfpFindSection(file,
816 _T("Strings"));
817
818 return INF_STATUS_SUCCESS;
819 }
820
821 /* EOF */