xref: /reactos/sdk/lib/inflib/infcore.c (revision ac43fd2b)
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 WCHAR       *start;       /* start position of item being parsed */
42   const WCHAR       *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   INFSTATUS        error;         /* error code */
52   unsigned int     token_len;     /* current token len */
53   WCHAR token[MAX_FIELD_LEN+1];   /* current token */
54 };
55 
56 typedef const WCHAR * (*parser_state_func)( struct parser *parser, const WCHAR *pos );
57 
58 /* parser state machine functions */
59 static const WCHAR *line_start_state( struct parser *parser, const WCHAR *pos );
60 static const WCHAR *section_name_state( struct parser *parser, const WCHAR *pos );
61 static const WCHAR *key_name_state( struct parser *parser, const WCHAR *pos );
62 static const WCHAR *value_name_state( struct parser *parser, const WCHAR *pos );
63 static const WCHAR *eol_backslash_state( struct parser *parser, const WCHAR *pos );
64 static const WCHAR *quotes_state( struct parser *parser, const WCHAR *pos );
65 static const WCHAR *leading_spaces_state( struct parser *parser, const WCHAR *pos );
66 static const WCHAR *trailing_spaces_state( struct parser *parser, const WCHAR *pos );
67 static const WCHAR *comment_state( struct parser *parser, const WCHAR *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                 PCWSTR 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 (strcmpiW(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                PCWSTR 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 = (ULONG)FIELD_OFFSET(INFCACHESECTION,
185                              Name[strlenW(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   Section->Id = ++Cache->NextSectionId;
195 
196   /* Copy section name */
197   strcpyW(Section->Name, Name);
198 
199   /* Append section */
200   if (Cache->FirstSection == NULL)
201     {
202       Cache->FirstSection = Section;
203       Cache->LastSection = Section;
204     }
205   else
206     {
207       Cache->LastSection->Next = Section;
208       Section->Prev = Cache->LastSection;
209       Cache->LastSection = Section;
210     }
211 
212   return Section;
213 }
214 
215 
216 PINFCACHELINE
217 InfpAddLine(PINFCACHESECTION Section)
218 {
219   PINFCACHELINE Line;
220 
221   if (Section == NULL)
222     {
223       DPRINT("Invalid parameter\n");
224       return NULL;
225     }
226 
227   Line = (PINFCACHELINE)MALLOC(sizeof(INFCACHELINE));
228   if (Line == NULL)
229     {
230       DPRINT("MALLOC() failed\n");
231       return NULL;
232     }
233   ZEROMEMORY(Line,
234              sizeof(INFCACHELINE));
235   Line->Id = ++Section->NextLineId;
236 
237   /* Append line */
238   if (Section->FirstLine == NULL)
239     {
240       Section->FirstLine = Line;
241       Section->LastLine = Line;
242     }
243   else
244     {
245       Section->LastLine->Next = Line;
246       Line->Prev = Section->LastLine;
247       Section->LastLine = Line;
248     }
249   Section->LineCount++;
250 
251   return Line;
252 }
253 
254 PINFCACHESECTION
255 InfpFindSectionById(PINFCACHE Cache, UINT Id)
256 {
257     PINFCACHESECTION Section;
258 
259     for (Section = Cache->FirstSection;
260          Section != NULL;
261          Section = Section->Next)
262     {
263         if (Section->Id == Id)
264         {
265             return Section;
266         }
267     }
268 
269     return NULL;
270 }
271 
272 PINFCACHESECTION
273 InfpGetSectionForContext(PINFCONTEXT Context)
274 {
275     PINFCACHE Cache;
276 
277     if (Context == NULL)
278     {
279         return NULL;
280     }
281 
282     Cache = (PINFCACHE)Context->Inf;
283     if (Cache == NULL)
284     {
285         return NULL;
286     }
287 
288     return InfpFindSectionById(Cache, Context->Section);
289 }
290 
291 PINFCACHELINE
292 InfpFindLineById(PINFCACHESECTION Section, UINT Id)
293 {
294     PINFCACHELINE Line;
295 
296     for (Line = Section->FirstLine;
297          Line != NULL;
298          Line = Line->Next)
299     {
300         if (Line->Id == Id)
301         {
302             return Line;
303         }
304     }
305 
306     return NULL;
307 }
308 
309 PINFCACHELINE
310 InfpGetLineForContext(PINFCONTEXT Context)
311 {
312     PINFCACHESECTION Section;
313 
314     Section = InfpGetSectionForContext(Context);
315     if (Section == NULL)
316     {
317         return NULL;
318     }
319 
320     return InfpFindLineById(Section, Context->Line);
321 }
322 
323 PVOID
324 InfpAddKeyToLine(PINFCACHELINE Line,
325                  PCWSTR Key)
326 {
327   if (Line == NULL)
328     {
329       DPRINT1("Invalid Line\n");
330       return NULL;
331     }
332 
333   if (Line->Key != NULL)
334     {
335       DPRINT1("Line already has a key\n");
336       return NULL;
337     }
338 
339   Line->Key = (PWCHAR)MALLOC((strlenW(Key) + 1) * sizeof(WCHAR));
340   if (Line->Key == NULL)
341     {
342       DPRINT1("MALLOC() failed\n");
343       return NULL;
344     }
345 
346   strcpyW(Line->Key, Key);
347 
348   return (PVOID)Line->Key;
349 }
350 
351 
352 PVOID
353 InfpAddFieldToLine(PINFCACHELINE Line,
354                    PCWSTR Data)
355 {
356   PINFCACHEFIELD Field;
357   ULONG Size;
358 
359   Size = (ULONG)FIELD_OFFSET(INFCACHEFIELD,
360                              Data[strlenW(Data) + 1]);
361   Field = (PINFCACHEFIELD)MALLOC(Size);
362   if (Field == NULL)
363     {
364       DPRINT1("MALLOC() failed\n");
365       return NULL;
366     }
367   ZEROMEMORY (Field,
368               Size);
369   strcpyW(Field->Data, Data);
370 
371   /* Append key */
372   if (Line->FirstField == NULL)
373     {
374       Line->FirstField = Field;
375       Line->LastField = Field;
376     }
377   else
378     {
379       Line->LastField->Next = Field;
380       Field->Prev = Line->LastField;
381       Line->LastField = Field;
382     }
383   Line->FieldCount++;
384 
385   return (PVOID)Field;
386 }
387 
388 
389 PINFCACHELINE
390 InfpFindKeyLine(PINFCACHESECTION Section,
391                 PCWSTR Key)
392 {
393   PINFCACHELINE Line;
394 
395   Line = Section->FirstLine;
396   while (Line != NULL)
397     {
398       if (Line->Key != NULL && strcmpiW(Line->Key, Key) == 0)
399         {
400           return Line;
401         }
402 
403       Line = Line->Next;
404     }
405 
406   return NULL;
407 }
408 
409 
410 /* push the current state on the parser stack */
411 __inline static void push_state( struct parser *parser, enum parser_state state )
412 {
413 //  assert( parser->stack_pos < sizeof(parser->stack)/sizeof(parser->stack[0]) );
414   parser->stack[parser->stack_pos++] = state;
415 }
416 
417 
418 /* pop the current state */
419 __inline static void pop_state( struct parser *parser )
420 {
421 //  assert( parser->stack_pos );
422   parser->state = parser->stack[--parser->stack_pos];
423 }
424 
425 
426 /* set the parser state and return the previous one */
427 __inline static enum parser_state set_state( struct parser *parser, enum parser_state state )
428 {
429   enum parser_state ret = parser->state;
430   parser->state = state;
431   return ret;
432 }
433 
434 
435 /* check if the pointer points to an end of file */
436 __inline static int is_eof( struct parser *parser, const WCHAR *ptr )
437 {
438   return (ptr >= parser->end || *ptr == CONTROL_Z || *ptr == 0);
439 }
440 
441 
442 /* check if the pointer points to an end of line */
443 __inline static int is_eol( struct parser *parser, const WCHAR *ptr )
444 {
445   return (ptr >= parser->end ||
446           *ptr == CONTROL_Z ||
447           *ptr == '\n' ||
448           (*ptr == '\r' && *(ptr + 1) == '\n') ||
449           *ptr == 0);
450 }
451 
452 
453 /* push data from current token start up to pos into the current token */
454 static int push_token( struct parser *parser, const WCHAR *pos )
455 {
456   UINT len = (UINT)(pos - parser->start);
457   const WCHAR *src = parser->start;
458   WCHAR *dst = parser->token + parser->token_len;
459 
460   if (len > MAX_FIELD_LEN - parser->token_len)
461     len = MAX_FIELD_LEN - parser->token_len;
462 
463   parser->token_len += len;
464   for ( ; len > 0; len--, dst++, src++)
465   {
466     if (*src)
467     {
468       *dst = *src;
469     }
470     else
471     {
472       *dst = ' ';
473     }
474   }
475 
476   *dst = 0;
477   parser->start = pos;
478 
479   return 0;
480 }
481 
482 
483 
484 /* add a section with the current token as name */
485 static PVOID add_section_from_token( struct parser *parser )
486 {
487   PINFCACHESECTION Section;
488 
489   if (parser->token_len > MAX_SECTION_NAME_LEN)
490     {
491       parser->error = INF_STATUS_SECTION_NAME_TOO_LONG;
492       return NULL;
493     }
494 
495   Section = InfpFindSection(parser->file,
496                             parser->token);
497   if (Section == NULL)
498     {
499       /* need to create a new one */
500       Section= InfpAddSection(parser->file,
501                               parser->token);
502       if (Section == NULL)
503         {
504           parser->error = INF_STATUS_NOT_ENOUGH_MEMORY;
505           return NULL;
506         }
507     }
508 
509   parser->token_len = 0;
510   parser->cur_section = Section;
511 
512   return (PVOID)Section;
513 }
514 
515 
516 /* add a field containing the current token to the current line */
517 static struct field *add_field_from_token( struct parser *parser, int is_key )
518 {
519   PVOID field;
520 
521   if (!parser->line)  /* need to start a new line */
522     {
523       if (parser->cur_section == NULL)  /* got a line before the first section */
524         {
525           parser->error = INF_STATUS_WRONG_INF_STYLE;
526           return NULL;
527         }
528 
529       parser->line = InfpAddLine(parser->cur_section);
530       if (parser->line == NULL)
531         goto error;
532     }
533   else
534     {
535 //      assert(!is_key);
536     }
537 
538   if (is_key)
539     {
540       field = InfpAddKeyToLine(parser->line, parser->token);
541     }
542   else
543     {
544       field = InfpAddFieldToLine(parser->line, parser->token);
545     }
546 
547   if (field != NULL)
548     {
549       parser->token_len = 0;
550       return field;
551     }
552 
553 error:
554   parser->error = INF_STATUS_NOT_ENOUGH_MEMORY;
555   return NULL;
556 }
557 
558 
559 /* close the current line and prepare for parsing a new one */
560 static void close_current_line( struct parser *parser )
561 {
562   parser->line = NULL;
563 }
564 
565 
566 
567 /* handler for parser LINE_START state */
568 static const WCHAR *line_start_state( struct parser *parser, const WCHAR *pos )
569 {
570   const WCHAR *p;
571 
572   for (p = pos; !is_eof( parser, p ); p++)
573     {
574       switch(*p)
575         {
576           case '\r':
577             continue;
578 
579           case '\n':
580             parser->line_pos++;
581             close_current_line( parser );
582             break;
583 
584           case ';':
585             push_state( parser, LINE_START );
586             set_state( parser, COMMENT );
587             return p + 1;
588 
589           case '[':
590             parser->start = p + 1;
591             set_state( parser, SECTION_NAME );
592             return p + 1;
593 
594           default:
595             if (!isspaceW(*p))
596               {
597                 parser->start = p;
598                 set_state( parser, KEY_NAME );
599                 return p;
600               }
601             break;
602         }
603     }
604   close_current_line( parser );
605   return NULL;
606 }
607 
608 
609 /* handler for parser SECTION_NAME state */
610 static const WCHAR *section_name_state( struct parser *parser, const WCHAR *pos )
611 {
612   const WCHAR *p;
613 
614   for (p = pos; !is_eol( parser, p ); p++)
615     {
616       if (*p == ']')
617         {
618           push_token( parser, p );
619           if (add_section_from_token( parser ) == NULL)
620             return NULL;
621           push_state( parser, LINE_START );
622           set_state( parser, COMMENT );  /* ignore everything else on the line */
623           return p + 1;
624         }
625     }
626   parser->error = INF_STATUS_BAD_SECTION_NAME_LINE; /* unfinished section name */
627   return NULL;
628 }
629 
630 
631 /* handler for parser KEY_NAME state */
632 static const WCHAR *key_name_state( struct parser *parser, const WCHAR *pos )
633 {
634     const WCHAR *p, *token_end = parser->start;
635 
636     for (p = pos; !is_eol( parser, p ); p++)
637     {
638         if (*p == ',') break;
639         switch(*p)
640         {
641 
642          case '=':
643             push_token( parser, token_end );
644             if (!add_field_from_token( parser, 1 )) return NULL;
645             parser->start = p + 1;
646             push_state( parser, VALUE_NAME );
647             set_state( parser, LEADING_SPACES );
648             return p + 1;
649         case ';':
650             push_token( parser, token_end );
651             if (!add_field_from_token( parser, 0 )) return NULL;
652             push_state( parser, LINE_START );
653             set_state( parser, COMMENT );
654             return p + 1;
655         case '"':
656             push_token( parser, token_end );
657             parser->start = p + 1;
658             push_state( parser, KEY_NAME );
659             set_state( parser, QUOTES );
660             return p + 1;
661         case '\\':
662             push_token( parser, token_end );
663             parser->start = p;
664             push_state( parser, KEY_NAME );
665             set_state( parser, EOL_BACKSLASH );
666             return p;
667         default:
668             if (!isspaceW(*p)) token_end = p + 1;
669             else
670             {
671                 push_token( parser, p );
672                 push_state( parser, KEY_NAME );
673                 set_state( parser, TRAILING_SPACES );
674                 return p;
675             }
676             break;
677         }
678     }
679     push_token( parser, token_end );
680     set_state( parser, VALUE_NAME );
681     return p;
682 }
683 
684 
685 /* handler for parser VALUE_NAME state */
686 static const WCHAR *value_name_state( struct parser *parser, const WCHAR *pos )
687 {
688     const WCHAR *p, *token_end = parser->start;
689 
690     for (p = pos; !is_eol( parser, p ); p++)
691     {
692         switch(*p)
693         {
694         case ';':
695             push_token( parser, token_end );
696             if (!add_field_from_token( parser, 0 )) return NULL;
697             push_state( parser, LINE_START );
698             set_state( parser, COMMENT );
699             return p + 1;
700         case ',':
701             push_token( parser, token_end );
702             if (!add_field_from_token( parser, 0 )) return NULL;
703             parser->start = p + 1;
704             push_state( parser, VALUE_NAME );
705             set_state( parser, LEADING_SPACES );
706             return p + 1;
707         case '"':
708             push_token( parser, token_end );
709             parser->start = p + 1;
710             push_state( parser, VALUE_NAME );
711             set_state( parser, QUOTES );
712             return p + 1;
713         case '\\':
714             push_token( parser, token_end );
715             parser->start = p;
716             push_state( parser, VALUE_NAME );
717             set_state( parser, EOL_BACKSLASH );
718             return p;
719         default:
720             if (!isspaceW(*p)) token_end = p + 1;
721             else
722             {
723                 push_token( parser, p );
724                 push_state( parser, VALUE_NAME );
725                 set_state( parser, TRAILING_SPACES );
726                 return p;
727             }
728             break;
729         }
730     }
731     push_token( parser, token_end );
732     if (!add_field_from_token( parser, 0 )) return NULL;
733     set_state( parser, LINE_START );
734     return p;
735 }
736 
737 
738 /* handler for parser EOL_BACKSLASH state */
739 static const WCHAR *eol_backslash_state( struct parser *parser, const WCHAR *pos )
740 {
741   const WCHAR *p;
742 
743   for (p = pos; !is_eof( parser, p ); p++)
744     {
745       switch(*p)
746         {
747           case '\r':
748             continue;
749 
750           case '\n':
751             parser->line_pos++;
752             parser->start = p + 1;
753             set_state( parser, LEADING_SPACES );
754             return p + 1;
755 
756           case '\\':
757             continue;
758 
759           case ';':
760             push_state( parser, EOL_BACKSLASH );
761             set_state( parser, COMMENT );
762             return p + 1;
763 
764           default:
765             if (isspaceW(*p))
766               continue;
767             push_token( parser, p );
768             pop_state( parser );
769             return p;
770         }
771     }
772   parser->start = p;
773   pop_state( parser );
774 
775   return p;
776 }
777 
778 
779 /* handler for parser QUOTES state */
780 static const WCHAR *quotes_state( struct parser *parser, const WCHAR *pos )
781 {
782   const WCHAR *p, *token_end = parser->start;
783 
784   for (p = pos; !is_eol( parser, p ); p++)
785     {
786       if (*p == '"')
787         {
788           if (p+1 < parser->end && p[1] == '"')  /* double quotes */
789             {
790               push_token( parser, p + 1 );
791               parser->start = token_end = p + 2;
792               p++;
793             }
794           else  /* end of quotes */
795             {
796               push_token( parser, p );
797               parser->start = p + 1;
798               pop_state( parser );
799               return p + 1;
800             }
801         }
802     }
803   push_token( parser, p );
804   pop_state( parser );
805   return p;
806 }
807 
808 
809 /* handler for parser LEADING_SPACES state */
810 static const WCHAR *leading_spaces_state( struct parser *parser, const WCHAR *pos )
811 {
812   const WCHAR *p;
813 
814   for (p = pos; !is_eol( parser, p ); p++)
815     {
816       if (*p == '\\')
817         {
818           parser->start = p;
819           set_state( parser, EOL_BACKSLASH );
820           return p;
821         }
822       if (!isspaceW(*p))
823         break;
824     }
825   parser->start = p;
826   pop_state( parser );
827   return p;
828 }
829 
830 
831 /* handler for parser TRAILING_SPACES state */
832 static const WCHAR *trailing_spaces_state( struct parser *parser, const WCHAR *pos )
833 {
834   const WCHAR *p;
835 
836   for (p = pos; !is_eol( parser, p ); p++)
837     {
838       if (*p == '\\')
839         {
840           set_state( parser, EOL_BACKSLASH );
841           return p;
842         }
843       if (!isspaceW(*p))
844         break;
845     }
846   pop_state( parser );
847   return p;
848 }
849 
850 
851 /* handler for parser COMMENT state */
852 static const WCHAR *comment_state( struct parser *parser, const WCHAR *pos )
853 {
854   const WCHAR *p = pos;
855 
856   while (!is_eol( parser, p ))
857      p++;
858   pop_state( parser );
859   return p;
860 }
861 
862 
863 /* parse a complete buffer */
864 INFSTATUS
865 InfpParseBuffer (PINFCACHE file,
866                  const WCHAR *buffer,
867                  const WCHAR *end,
868                  PULONG error_line)
869 {
870   struct parser parser;
871   const WCHAR *pos = buffer;
872 
873   parser.start       = buffer;
874   parser.end         = end;
875   parser.file        = file;
876   parser.line        = NULL;
877   parser.state       = LINE_START;
878   parser.stack_pos   = 0;
879   parser.cur_section = NULL;
880   parser.line_pos    = 1;
881   parser.error       = 0;
882   parser.token_len   = 0;
883 
884   /* parser main loop */
885   while (pos)
886     pos = (parser_funcs[parser.state])(&parser, pos);
887 
888   if (parser.error)
889     {
890       if (error_line)
891         *error_line = parser.line_pos;
892       return parser.error;
893     }
894 
895   /* find the [strings] section */
896   file->StringsSection = InfpFindSection(file,
897                                          L"Strings");
898 
899   return INF_STATUS_SUCCESS;
900 }
901 
902 /* EOF */
903