1## @file ParserValidate.py
2# Functions for parser validation
3#
4# Copyright (c) 2011 - 2014, Intel Corporation. All rights reserved.<BR>
5#
6# This program and the accompanying materials are licensed and made available
7# under the terms and conditions of the BSD License which accompanies this
8# distribution. The full text of the license may be found at
9# http://opensource.org/licenses/bsd-license.php
10#
11# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13#
14
15'''
16PaserValidate
17'''
18
19import os.path
20import re
21import platform
22
23from Library.DataType import MODULE_LIST
24from Library.DataType import COMPONENT_TYPE_LIST
25from Library.DataType import PCD_USAGE_TYPE_LIST_OF_MODULE
26from Library.DataType import TAB_SPACE_SPLIT
27from Library.String import GetSplitValueList
28from Library.ExpressionValidate import IsValidBareCString
29from Library.ExpressionValidate import IsValidFeatureFlagExp
30from Common.MultipleWorkspace import MultipleWorkspace as mws
31
32## __HexDigit() method
33#
34# Whether char input is a Hex data bit
35#
36# @param  TempChar:    The char to test
37#
38def __HexDigit(TempChar):
39    if (TempChar >= 'a' and TempChar <= 'f') or \
40    (TempChar >= 'A' and TempChar <= 'F') \
41            or (TempChar >= '0' and TempChar <= '9'):
42        return True
43    else:
44        return False
45
46## IsValidHex() method
47#
48# Whether char input is a Hex data.
49#
50# @param  TempChar:    The char to test
51#
52def IsValidHex(HexStr):
53    if not HexStr.upper().startswith("0X"):
54        return False
55    CharList = [c for c in HexStr[2:] if not __HexDigit(c)]
56    if len(CharList) == 0:
57        return True
58    else:
59        return False
60
61## Judge the input string is valid bool type or not.
62#
63# <TRUE>                  ::=  {"TRUE"} {"true"} {"True"} {"0x1"} {"0x01"}
64# <FALSE>                 ::=  {"FALSE"} {"false"} {"False"} {"0x0"} {"0x00"}
65# <BoolType>              ::=  {<TRUE>} {<FALSE>}
66#
67# @param    BoolString:    A string contained the value need to be judged.
68#
69def IsValidBoolType(BoolString):
70    #
71    # Valid Ture
72    #
73    if BoolString == 'TRUE' or \
74       BoolString == 'True' or \
75       BoolString == 'true' or \
76       BoolString == '0x1' or \
77       BoolString == '0x01':
78        return True
79    #
80    # Valid False
81    #
82    elif BoolString == 'FALSE' or \
83         BoolString == 'False' or \
84         BoolString == 'false' or \
85         BoolString == '0x0' or \
86         BoolString == '0x00':
87        return True
88    #
89    # Invalid bool type
90    #
91    else:
92        return False
93
94## Is Valid Module Type List or not
95#
96# @param      ModuleTypeList:  A list contain ModuleType strings need to be
97# judged.
98#
99def IsValidInfMoudleTypeList(ModuleTypeList):
100    for ModuleType in ModuleTypeList:
101        return IsValidInfMoudleType(ModuleType)
102
103## Is Valid Module Type or not
104#
105# @param      ModuleType:  A string contain ModuleType need to be judged.
106#
107def IsValidInfMoudleType(ModuleType):
108    if ModuleType in MODULE_LIST:
109        return True
110    else:
111        return False
112
113## Is Valid Component Type or not
114#
115# @param      ComponentType:  A string contain ComponentType need to be judged.
116#
117def IsValidInfComponentType(ComponentType):
118    if ComponentType.upper() in COMPONENT_TYPE_LIST:
119        return True
120    else:
121        return False
122
123
124## Is valid Tool Family or not
125#
126# @param   ToolFamily:   A string contain Tool Family need to be judged.
127# Famlily := [A-Z]([a-zA-Z0-9])*
128#
129def IsValidToolFamily(ToolFamily):
130    ReIsValieFamily = re.compile(r"^[A-Z]+[A-Za-z0-9]{0,}$", re.DOTALL)
131    if ReIsValieFamily.match(ToolFamily) == None:
132        return False
133    return True
134
135## Is valid Tool TagName or not
136#
137# The TagName sample is MYTOOLS and VS2005.
138#
139# @param   TagName:   A string contain Tool TagName need to be judged.
140#
141def IsValidToolTagName(TagName):
142    if TagName.strip() == '':
143        return True
144    if TagName.strip() == '*':
145        return True
146    if not IsValidWord(TagName):
147        return False
148    return True
149
150## Is valid arch or not
151#
152# @param Arch   The arch string need to be validated
153# <OA>                  ::=  (a-zA-Z)(A-Za-z0-9){0,}
154# <arch>                 ::=  {"IA32"} {"X64"} {"IPF"} {"EBC"} {<OA>}
155#                            {"common"}
156# @param   Arch:   Input arch
157#
158def IsValidArch(Arch):
159    if Arch == 'common':
160        return True
161    ReIsValieArch = re.compile(r"^[a-zA-Z]+[a-zA-Z0-9]{0,}$", re.DOTALL)
162    if ReIsValieArch.match(Arch) == None:
163        return False
164    return True
165
166## Is valid family or not
167#
168# <Family>        ::=  {"MSFT"} {"GCC"} {"INTEL"} {<Usr>} {"*"}
169# <Usr>           ::=  [A-Z][A-Za-z0-9]{0,}
170#
171# @param family:   The family string need to be validated
172#
173def IsValidFamily(Family):
174    Family = Family.strip()
175    if Family == '*':
176        return True
177
178    if Family == '':
179        return True
180
181    ReIsValidFamily = re.compile(r"^[A-Z]+[A-Za-z0-9]{0,}$", re.DOTALL)
182    if ReIsValidFamily.match(Family) == None:
183        return False
184    return True
185
186## Is valid build option name or not
187#
188# @param BuildOptionName:   The BuildOptionName string need to be validated
189#
190def IsValidBuildOptionName(BuildOptionName):
191    if not BuildOptionName:
192        return False
193
194    ToolOptionList = GetSplitValueList(BuildOptionName, '_', 4)
195
196    if len(ToolOptionList) != 5:
197        return False
198
199    ReIsValidBuildOption1 = re.compile(r"^\s*(\*)|([A-Z][a-zA-Z0-9]*)$")
200    ReIsValidBuildOption2 = re.compile(r"^\s*(\*)|([a-zA-Z][a-zA-Z0-9]*)$")
201
202    if ReIsValidBuildOption1.match(ToolOptionList[0]) == None:
203        return False
204
205    if ReIsValidBuildOption1.match(ToolOptionList[1]) == None:
206        return False
207
208    if ReIsValidBuildOption2.match(ToolOptionList[2]) == None:
209        return False
210
211    if ToolOptionList[3] == "*" and ToolOptionList[4] not in ['FAMILY', 'DLL', 'DPATH']:
212        return False
213
214    return True
215
216## IsValidToken
217#
218# Check if pattern string matches total token
219#
220# @param ReString:     regular string
221# @param Token:        Token to be matched
222#
223def IsValidToken(ReString, Token):
224    Match = re.compile(ReString).match(Token)
225    return Match and Match.start() == 0 and Match.end() == len(Token)
226
227## IsValidPath
228#
229# Check if path exist
230#
231# @param Path: Absolute path or relative path to be checked
232# @param Root: Root path
233#
234def IsValidPath(Path, Root):
235    Path = Path.strip()
236    OrigPath = Path.replace('\\', '/')
237
238    Path = os.path.normpath(Path).replace('\\', '/')
239    Root = os.path.normpath(Root).replace('\\', '/')
240    FullPath = mws.join(Root, Path)
241
242    if not os.path.exists(FullPath):
243        return False
244
245    #
246    # If Path is absolute path.
247    # It should be in Root.
248    #
249    if os.path.isabs(Path):
250        if not Path.startswith(Root):
251            return False
252        return True
253
254    #
255    # Check illegal character
256    #
257    for Rel in ['/', './', '../']:
258        if OrigPath.startswith(Rel):
259            return False
260    for Rel in ['//', '/./', '/../']:
261        if Rel in OrigPath:
262            return False
263    for Rel in ['/.', '/..', '/']:
264        if OrigPath.endswith(Rel):
265            return False
266
267    Path = Path.rstrip('/')
268
269    #
270    # Check relative path
271    #
272    for Word in Path.split('/'):
273        if not IsValidWord(Word):
274            return False
275
276    return True
277
278## IsValidInstallPath
279#
280# Check if an install path valid or not.
281#
282# Absolute path or path starts with '.' or path contains '..' are invalid.
283#
284# @param Path: path to be checked
285#
286def IsValidInstallPath(Path):
287    if platform.platform().find("Windows") >= 0:
288        if os.path.isabs(Path):
289            return False
290    else:
291        if Path[1:2] == ':':
292            return False
293        if os.path.isabs(Path):
294            return False
295    if Path.startswith('.'):
296        return False
297
298    if Path.find('..') != -1:
299        return False
300
301    return True
302
303
304## IsValidCFormatGuid
305#
306# Check if GUID format has the from of {8,4,4,{2,2,2,2,2,2,2,2}}
307#
308# @param Guid: Guid to be checked
309#
310def IsValidCFormatGuid(Guid):
311    #
312    # Valid: { 0xf0b11735, 0x87a0, 0x4193, {0xb2, 0x66, 0x53, 0x8c, 0x38,
313    #        0xaf, 0x48, 0xce }}
314    # Invalid: { 0xf0b11735, 0x87a0, 0x4193, {0xb2, 0x66, 0x53, 0x8c, 0x38,
315    #          0xaf, 0x48, 0xce }} 0x123
316    # Invalid: { 0xf0b1 1735, 0x87a0, 0x4193, {0xb2, 0x66, 0x53, 0x8c, 0x38,
317    #          0xaf, 0x48, 0xce }}
318    #
319    List = ['{', 10, ',', 6, ',', 6, ',{', 4, ',', 4, ',', 4,
320            ',', 4, ',', 4, ',', 4, ',', 4, ',', 4, '}}']
321    Index = 0
322    Value = ''
323    SepValue = ''
324    for Char in Guid:
325        if Char not in '{},\t ':
326            Value += Char
327            continue
328        if Value:
329            try:
330                #
331                # Index may out of bound
332                #
333                if not SepValue or SepValue != List[Index]:
334                    return False
335                Index += 1
336                SepValue = ''
337
338                if not Value.startswith('0x') and not Value.startswith('0X'):
339                    return False
340
341                #
342                # Index may out of bound
343                #
344                if type(List[Index]) != type(1) or \
345                   len(Value) > List[Index] or len(Value) < 3:
346                    return False
347
348                #
349                # Check if string can be converted to integer
350                # Throw exception if not
351                #
352                int(Value, 16)
353            except BaseException:
354                #
355                # Exception caught means invalid format
356                #
357                return False
358            Value = ''
359            Index += 1
360        if Char in '{},':
361            SepValue += Char
362
363    return SepValue == '}}' and Value == ''
364
365## IsValidPcdType
366#
367# Check whether the PCD type is valid
368#
369# @param PcdTypeString: The PcdType string need to be checked.
370#
371def IsValidPcdType(PcdTypeString):
372    if PcdTypeString.upper() in PCD_USAGE_TYPE_LIST_OF_MODULE:
373        return True
374    else:
375        return False
376
377## IsValidWord
378#
379# Check whether the word is valid.
380# <Word>   ::=  (a-zA-Z0-9_)(a-zA-Z0-9_-){0,} Alphanumeric characters with
381#               optional
382#               dash "-" and/or underscore "_" characters. No whitespace
383#               characters are permitted.
384#
385# @param Word:  The word string need to be checked.
386#
387def IsValidWord(Word):
388    if not Word:
389        return False
390    #
391    # The first char should be alpha, _ or Digit.
392    #
393    if not Word[0].isalnum() and \
394       not Word[0] == '_' and \
395       not Word[0].isdigit():
396        return False
397
398    LastChar = ''
399    for Char in Word[1:]:
400        if (not Char.isalpha()) and \
401           (not Char.isdigit()) and \
402           Char != '-' and \
403           Char != '_' and \
404           Char != '.':
405            return False
406        if Char == '.' and LastChar == '.':
407            return False
408        LastChar = Char
409
410    return True
411
412
413## IsValidSimpleWord
414#
415# Check whether the SimpleWord is valid.
416# <SimpleWord>          ::=  (a-zA-Z0-9)(a-zA-Z0-9_-){0,}
417#                       A word that cannot contain a period character.
418#
419# @param Word:  The word string need to be checked.
420#
421def IsValidSimpleWord(Word):
422    ReIsValidSimpleWord = \
423        re.compile(r"^[0-9A-Za-z][0-9A-Za-z\-_]*$", re.DOTALL)
424    Word = Word.strip()
425    if not Word:
426        return False
427
428    if not ReIsValidSimpleWord.match(Word):
429        return False
430
431    return True
432
433## IsValidDecVersion
434#
435# Check whether the decimal version is valid.
436# <DecVersion>          ::=  (0-9){1,} ["." (0-9){1,}]
437#
438# @param Word:  The word string need to be checked.
439#
440def IsValidDecVersion(Word):
441    if Word.find('.') > -1:
442        ReIsValidDecVersion = re.compile(r"[0-9]+\.?[0-9]+$")
443    else:
444        ReIsValidDecVersion = re.compile(r"[0-9]+$")
445    if ReIsValidDecVersion.match(Word) == None:
446        return False
447    return True
448
449## IsValidHexVersion
450#
451# Check whether the hex version is valid.
452# <HexVersion>          ::=  "0x" <Major> <Minor>
453# <Major>               ::=  <HexDigit>{4}
454# <Minor>               ::=  <HexDigit>{4}
455#
456# @param Word:  The word string need to be checked.
457#
458def IsValidHexVersion(Word):
459    ReIsValidHexVersion = re.compile(r"[0][xX][0-9A-Fa-f]{8}$", re.DOTALL)
460    if ReIsValidHexVersion.match(Word) == None:
461        return False
462
463    return True
464
465## IsValidBuildNumber
466#
467# Check whether the BUILD_NUMBER is valid.
468# ["BUILD_NUMBER" "=" <Integer>{1,4} <EOL>]
469#
470# @param Word:  The BUILD_NUMBER string need to be checked.
471#
472def IsValidBuildNumber(Word):
473    ReIsValieBuildNumber = re.compile(r"[0-9]{1,4}$", re.DOTALL)
474    if ReIsValieBuildNumber.match(Word) == None:
475        return False
476
477    return True
478
479## IsValidDepex
480#
481# Check whether the Depex is valid.
482#
483# @param Word:  The Depex string need to be checked.
484#
485def IsValidDepex(Word):
486    Index = Word.upper().find("PUSH")
487    if Index > -1:
488        return IsValidCFormatGuid(Word[Index+4:].strip())
489
490    ReIsValidCName = re.compile(r"^[A-Za-z_][0-9A-Za-z_\s\.]*$", re.DOTALL)
491    if ReIsValidCName.match(Word) == None:
492        return False
493
494    return True
495
496## IsValidNormalizedString
497#
498# Check
499# <NormalizedString>    ::=  <DblQuote> [{<Word>} {<Space>}]{1,} <DblQuote>
500# <Space>               ::=  0x20
501#
502# @param String: string to be checked
503#
504def IsValidNormalizedString(String):
505    if String == '':
506        return True
507
508    for Char in String:
509        if Char == '\t':
510            return False
511
512    StringList = GetSplitValueList(String, TAB_SPACE_SPLIT)
513
514    for Item in StringList:
515        if not Item:
516            continue
517        if not IsValidWord(Item):
518            return False
519
520    return True
521
522## IsValidIdString
523#
524# Check whether the IdString is valid.
525#
526# @param IdString:  The IdString need to be checked.
527#
528def IsValidIdString(String):
529    if IsValidSimpleWord(String.strip()):
530        return True
531
532    if String.strip().startswith('"') and \
533       String.strip().endswith('"'):
534        String = String[1:-1]
535        if String.strip() == "":
536            return True
537        if IsValidNormalizedString(String):
538            return True
539
540    return False
541
542## IsValidVersionString
543#
544# Check whether the VersionString is valid.
545# <AsciiString>           ::=  [ [<WhiteSpace>]{0,} [<AsciiChars>]{0,} ] {0,}
546# <WhiteSpace>            ::=  {<Tab>} {<Space>}
547# <Tab>                   ::=  0x09
548# <Space>                 ::=  0x20
549# <AsciiChars>            ::=  (0x21 - 0x7E)
550#
551# @param VersionString:  The VersionString need to be checked.
552#
553def IsValidVersionString(VersionString):
554    VersionString = VersionString.strip()
555    for Char in VersionString:
556        if not (Char >= 0x21 and Char <= 0x7E):
557            return False
558
559    return True
560
561## IsValidPcdValue
562#
563# Check whether the PcdValue is valid.
564#
565# @param VersionString:  The PcdValue need to be checked.
566#
567def IsValidPcdValue(PcdValue):
568    for Char in PcdValue:
569        if Char == '\n' or Char == '\t' or Char == '\f':
570            return False
571
572    #
573    # <Boolean>
574    #
575    if IsValidFeatureFlagExp(PcdValue, True)[0]:
576        return True
577
578    #
579    # <Number>                ::=  {<Integer>} {<HexNumber>}
580    # <Integer>               ::=  {(0-9)} {(1-9)(0-9){1,}}
581    # <HexNumber>             ::=  "0x" <HexDigit>{1,}
582    # <HexDigit>              ::=  (a-fA-F0-9)
583    #
584    if IsValidHex(PcdValue):
585        return True
586
587    ReIsValidIntegerSingle = re.compile(r"^\s*[0-9]\s*$", re.DOTALL)
588    if ReIsValidIntegerSingle.match(PcdValue) != None:
589        return True
590
591    ReIsValidIntegerMulti = re.compile(r"^\s*[1-9][0-9]+\s*$", re.DOTALL)
592    if ReIsValidIntegerMulti.match(PcdValue) != None:
593        return True
594
595    #
596    # <StringVal>              ::=  {<StringType>} {<Array>} {"$(" <MACRO> ")"}
597    # <StringType>             ::=  {<UnicodeString>} {<CString>}
598    #
599    ReIsValidStringType = re.compile(r"^\s*[\"L].*[\"]\s*$")
600    if ReIsValidStringType.match(PcdValue):
601        IsTrue = False
602        if PcdValue.strip().startswith('L\"'):
603            StringValue = PcdValue.strip().lstrip('L\"').rstrip('\"')
604            if IsValidBareCString(StringValue):
605                IsTrue = True
606        elif PcdValue.strip().startswith('\"'):
607            StringValue = PcdValue.strip().lstrip('\"').rstrip('\"')
608            if IsValidBareCString(StringValue):
609                IsTrue = True
610        if IsTrue:
611            return IsTrue
612
613    #
614    # <Array>                 ::=   {<CArray>} {<NList>} {<CFormatGUID>}
615    # <CArray>                ::=   "{" [<NList>] <CArray>{0,} "}"
616    # <NList>                 ::=   <HexByte> ["," <HexByte>]{0,}
617    # <HexDigit>              ::=  (a-fA-F0-9)
618    # <HexByte>               ::=  "0x" <HexDigit>{1,2}
619    #
620    if IsValidCFormatGuid(PcdValue):
621        return True
622
623    ReIsValidByteHex = re.compile(r"^\s*0x[0-9a-fA-F]{1,2}\s*$", re.DOTALL)
624    if PcdValue.strip().startswith('{') and PcdValue.strip().endswith('}') :
625        StringValue = PcdValue.strip().lstrip('{').rstrip('}')
626        ValueList = StringValue.split(',')
627        AllValidFlag = True
628        for ValueItem in ValueList:
629            if not ReIsValidByteHex.match(ValueItem.strip()):
630                AllValidFlag = False
631
632        if AllValidFlag:
633            return True
634
635    #
636    # NList
637    #
638    AllValidFlag = True
639    ValueList = PcdValue.split(',')
640    for ValueItem in ValueList:
641        if not ReIsValidByteHex.match(ValueItem.strip()):
642            AllValidFlag = False
643
644    if AllValidFlag:
645        return True
646
647    return False
648
649## IsValidCVariableName
650#
651# Check whether the PcdValue is valid.
652#
653# @param VersionString:  The PcdValue need to be checked.
654#
655def IsValidCVariableName(CName):
656    ReIsValidCName = re.compile(r"^[A-Za-z_][0-9A-Za-z_]*$", re.DOTALL)
657    if ReIsValidCName.match(CName) == None:
658        return False
659
660    return True
661
662## IsValidIdentifier
663#
664# <Identifier> ::= <NonDigit> <Chars>{0,}
665# <Chars> ::= (a-zA-Z0-9_)
666# <NonDigit> ::= (a-zA-Z_)
667#
668# @param Ident: identifier to be checked
669#
670def IsValidIdentifier(Ident):
671    ReIdent = re.compile(r"^[A-Za-z_][0-9A-Za-z_]*$", re.DOTALL)
672    if ReIdent.match(Ident) == None:
673        return False
674
675    return True
676
677## IsValidDecVersionVal
678#
679# {(0-9){1,} "." (0-99)}
680#
681# @param Ver: version to be checked
682#
683def IsValidDecVersionVal(Ver):
684    ReVersion = re.compile(r"[0-9]+(\.[0-9]{1,2})$")
685
686    if ReVersion.match(Ver) == None:
687        return False
688
689    return True
690
691
692## IsValidLibName
693#
694# (A-Z)(a-zA-Z0-9){0,} and could not be "NULL"
695#
696def IsValidLibName(LibName):
697    if LibName == 'NULL':
698        return False
699    ReLibName = re.compile("^[A-Z]+[a-zA-Z0-9]*$")
700    if not ReLibName.match(LibName):
701        return False
702
703    return True
704
705# IsValidUserId
706#
707# <UserId> ::= (a-zA-Z)(a-zA-Z0-9_.){0,}
708# Words that contain period "." must be encapsulated in double quotation marks.
709#
710def IsValidUserId(UserId):
711    UserId = UserId.strip()
712    Quoted = False
713    if UserId.startswith('"') and UserId.endswith('"'):
714        Quoted = True
715        UserId = UserId[1:-1]
716    if not UserId or not UserId[0].isalpha():
717        return False
718    for Char in UserId[1:]:
719        if not Char.isalnum() and not Char in '_.':
720            return False
721        if Char == '.' and not Quoted:
722            return False
723    return True
724
725#
726# Check if a UTF16-LE file has a BOM header
727#
728def CheckUTF16FileHeader(File):
729    FileIn = open(File, 'rb').read(2)
730    if FileIn != '\xff\xfe':
731        return False
732
733    return True
734