mmidy 2002/06/27 08:01:19 Modified: java/src/org/apache/xpath/compiler XPathParser.java java/src/org/apache/xpath/res XPATHErrorResources.java XPATHErrorResources.properties Log: Bugzilla 5016: Patch from Henry Zongaro... Fix XPATHParser.java to prevent matching empty RelativeLocationPath and Step expressions, prevent a LocationPath from preceding a Predicate in a FilterExpr, check for valid NameTest in NodeTest() Revision Changes Path 1.21 +274 -96 xml-xalan/java/src/org/apache/xpath/compiler/XPathParser.java Index: XPathParser.java =================================================================== RCS file: /home/cvs/xml-xalan/java/src/org/apache/xpath/compiler/XPathParser.java,v retrieving revision 1.20 retrieving revision 1.21 diff -u -r1.20 -r1.21 --- XPathParser.java 6 May 2002 18:45:28 -0000 1.20 +++ XPathParser.java 27 Jun 2002 15:01:19 -0000 1.21 @@ -110,6 +110,13 @@ int m_queueMark = 0; /** + * Results from checking FilterExpr syntax + */ + protected final static int FILTER_MATCH_FAILED = 0; + protected final static int FILTER_MATCH_PRIMARY = 1; + protected final static int FILTER_MATCH_PREDICATES = 2; + + /** * The parser constructor. */ public XPathParser(ErrorListener errorListener, javax.xml.transform.SourceLocator sourceLocator) @@ -1256,23 +1263,47 @@ { int opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; - boolean foundLocationPath; - FilterExpr(); + int filterExprMatch = FilterExpr(); - if (tokenIs('/')) + if (filterExprMatch != FILTER_MATCH_FAILED) { - nextToken(); + // If FilterExpr had Predicates, a OP_LOCATIONPATH opcode would already + // have been inserted. + boolean locationPathStarted = (filterExprMatch==FILTER_MATCH_PREDICATES); + + if (tokenIs('/')) + { + nextToken(); + + if (!locationPathStarted) + { + // int locationPathOpPos = opPos; + insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH); + + locationPathStarted = true; + } + + if (!RelativeLocationPath()) + { + // "Relative location path expected following '/' or '//'" + error(XPATHErrorResources.ER_EXPECTED_REL_LOC_PATH, null); + } - // int locationPathOpPos = opPos; - insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH); - RelativeLocationPath(); + } // Terminate for safety. - m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]] = OpCodes.ENDOP; - m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; - m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = - m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + if (locationPathStarted) + { + m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]] = OpCodes.ENDOP; + m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; + m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = + m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + } + } + else + { + LocationPath(); } } @@ -1285,40 +1316,48 @@ * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide * the error condition is severe enough to halt processing. * + * @return FILTER_MATCH_PREDICATES, if this method successfully matched a + * FilterExpr with one or more Predicates; + * FILTER_MATCH_PRIMARY, if this method successfully matched a + * FilterExpr that was just a PrimaryExpr; or + * FILTER_MATCH_FAILED, if this method did not match a FilterExpr + * * @throws javax.xml.transform.TransformerException */ - protected void FilterExpr() throws javax.xml.transform.TransformerException + protected int FilterExpr() throws javax.xml.transform.TransformerException { int opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; - // boolean isFunc = lookahead('(', 1); - PrimaryExpr(); + int filterMatch; - if (tokenIs('[')) + if (PrimaryExpr()) { + if (tokenIs('[')) + { - // int locationPathOpPos = opPos; - insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH); + // int locationPathOpPos = opPos; + insertOp(opPos, 2, OpCodes.OP_LOCATIONPATH); - while (tokenIs('[')) - { - Predicate(); - } + while (tokenIs('[')) + { + Predicate(); + } - if (tokenIs('/')) + filterMatch = FILTER_MATCH_PREDICATES; + } + else { - nextToken(); - RelativeLocationPath(); + filterMatch = FILTER_MATCH_PRIMARY; } - - // Terminate for safety. - m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]] = OpCodes.ENDOP; - m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; - m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = - m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + } + else + { + filterMatch = FILTER_MATCH_FAILED; } + return filterMatch; + /* * if(tokenIs('[')) * { @@ -1336,12 +1375,15 @@ * | Number * | FunctionCall * + * @return true if this method successfully matched a PrimaryExpr * * @throws javax.xml.transform.TransformerException + * */ - protected void PrimaryExpr() throws javax.xml.transform.TransformerException + protected boolean PrimaryExpr() throws javax.xml.transform.TransformerException { + boolean matchFound; int opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; if ((m_tokenChar == '\'') || (m_tokenChar == '"')) @@ -1351,6 +1393,8 @@ m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + matchFound = true; } else if (m_tokenChar == '$') { @@ -1360,6 +1404,8 @@ m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + matchFound = true; } else if (m_tokenChar == '(') { @@ -1370,6 +1416,8 @@ m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + matchFound = true; } else if ((null != m_token) && ((('.' == m_tokenChar) && (m_token.length() > 1) && Character.isDigit( m_token.charAt(1))) || Character.isDigit(m_tokenChar))) @@ -1379,15 +1427,19 @@ m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + matchFound = true; } else if (lookahead('(', 1) || (lookahead(':', 1) && lookahead('(', 3))) { - FunctionCall(); + matchFound = FunctionCall(); } else { - LocationPath(); + matchFound = false; } + + return matchFound; } /** @@ -1413,10 +1465,11 @@ * * FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')' * + * @return true if, and only if, a FunctionCall was matched * * @throws javax.xml.transform.TransformerException */ - protected void FunctionCall() throws javax.xml.transform.TransformerException + protected boolean FunctionCall() throws javax.xml.transform.TransformerException { int opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; @@ -1450,9 +1503,8 @@ case OpCodes.NODETYPE_COMMENT : case OpCodes.NODETYPE_TEXT : case OpCodes.NODETYPE_NODE : - LocationPath(); - - return; + // Node type tests look like function calls, but they're not + return false; default : appendOp(3, OpCodes.OP_FUNCTION); @@ -1492,6 +1544,8 @@ m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + return true; } // ============= GRAMMAR FUNCTIONS ================= @@ -1512,7 +1566,9 @@ // int locationPathOpPos = opPos; appendOp(2, OpCodes.OP_LOCATIONPATH); - if (tokenIs('/')) + boolean seenSlash = tokenIs('/'); + + if (seenSlash) { appendOp(4, OpCodes.FROM_ROOT); @@ -1526,7 +1582,13 @@ if (m_token != null) { - RelativeLocationPath(); + if (!RelativeLocationPath() && !seenSlash) + { + // Neither a '/' nor a RelativeLocationPath - i.e., matched nothing + // "Location path expected, but found "+m_token+" was encountered." + error(XPATHErrorResources.ER_EXPECTED_LOC_PATH, + new Object [] {m_token}); + } } // Terminate for safety. @@ -1542,19 +1604,31 @@ * | RelativeLocationPath '/' Step * | AbbreviatedRelativeLocationPath * + * @returns true if, and only if, a RelativeLocationPath was matched * * @throws javax.xml.transform.TransformerException */ - protected void RelativeLocationPath() throws javax.xml.transform.TransformerException + protected boolean RelativeLocationPath() + throws javax.xml.transform.TransformerException { - - Step(); + if (!Step()) + { + return false; + } while (tokenIs('/')) { nextToken(); - Step(); + + if (!Step()) + { + // RelativeLocationPath can't end with a trailing '/' + // "Location step expected following '/' or '//'" + error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null); + } } + + return true; } /** @@ -1562,13 +1636,47 @@ * Step ::= Basis Predicate * | AbbreviatedStep * + * @returns false if step was empty (or only a '/'); true, otherwise + * * @throws javax.xml.transform.TransformerException */ - protected void Step() throws javax.xml.transform.TransformerException + protected boolean Step() throws javax.xml.transform.TransformerException { - int opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; + boolean doubleSlash = tokenIs('/'); + + // At most a single '/' before each Step is consumed by caller; if the + // first thing is a '/', that means we had '//' and the Step must not + // be empty. + if (doubleSlash) + { + nextToken(); + + appendOp(2, OpCodes.FROM_DESCENDANTS_OR_SELF); + + // Have to fix up for patterns such as '//@foo' or '//attribute::foo', + // which translate to 'descendant-or-self::node()/attribute::foo'. + // notice I leave the '/' on the queue, so the next will be processed + // by a regular step pattern. + + // Make room for telling how long the step is without the predicate + m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; + m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]] = + OpCodes.NODETYPE_NODE; + m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; + + // Tell how long the step is without the predicate + m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH + 1] = + m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + // Tell how long the step is with the predicate + m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = + m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; + } + if (tokenIs(".")) { nextToken(); @@ -1599,8 +1707,8 @@ // There is probably a better way to test for this // transition... but it gets real hairy if you try // to do it in basis(). - else if (tokenIs('*') || tokenIs('@') || tokenIs('/') - || tokenIs('_') || (m_token!= null && Character.isLetter(m_token.charAt(0)))) + else if (tokenIs('*') || tokenIs('@') || tokenIs('_') + || (m_token!= null && Character.isLetter(m_token.charAt(0)))) { Basis(); @@ -1613,6 +1721,19 @@ m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; } + else + { + // No Step matched - that's an error if previous thing was a '//' + if (doubleSlash) + { + // "Location step expected following '/' or '//'" + error(XPATHErrorResources.ER_EXPECTED_LOC_STEP, null); + } + + return false; + } + + return true; } /** @@ -1643,37 +1764,6 @@ appendOp(2, axesType); nextToken(); } - else if (tokenIs('/')) - { - axesType = OpCodes.FROM_DESCENDANTS_OR_SELF; - - appendOp(2, axesType); - - // Have to fix up for patterns such as '//@foo' or '//attribute::foo', - // which translate to 'descendant-or-self::node()/attribute::foo'. - // notice I leave the '/' on the queue, so the next will be processed - // by a regular step pattern. - // if(lookahead('@', 1) || lookahead("::", 2)) - { - - // Make room for telling how long the step is without the predicate - m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; - m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]] = - OpCodes.NODETYPE_NODE; - m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; - - // Tell how long the step is without the predicate - m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH + 1] = - m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; - - return; // make a quick exit... - } - - // else - // { - // nextToken(); - // } - } else { axesType = OpCodes.FROM_CHILDREN; @@ -1780,6 +1870,14 @@ { m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]] = m_queueMark - 1; + + // Minimalist check for an NCName - just check first character + // to distinguish from other possible tokens + if (!Character.isLetter(m_tokenChar) && !tokenIs('_')) + { + // "Node test that matches either NCName:* or QName was expected." + error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null); + } } nextToken(); @@ -1810,6 +1908,14 @@ } m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]] = m_queueMark - 1; + + // Minimalist check for an NCName - just check first character + // to distinguish from other possible tokens + if (!Character.isLetter(m_tokenChar) && !tokenIs('_')) + { + // "Node test that matches either NCName:* or QName was expected." + error(XPATHErrorResources.ER_EXPECTED_NODE_TEST, null); + } } m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] += 1; @@ -2025,6 +2131,12 @@ int opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; + final int RELATIVE_PATH_NOT_PERMITTED = 0; + final int RELATIVE_PATH_PERMITTED = 1; + final int RELATIVE_PATH_REQUIRED = 2; + + int relativePathStatus = RELATIVE_PATH_NOT_PERMITTED; + appendOp(2, OpCodes.OP_LOCATIONPATHPATTERN); if (lookahead('(', 1) @@ -2033,17 +2145,27 @@ { IdKeyPattern(); - if (tokenIs('/') && lookahead('/', 1)) + if (tokenIs('/')) { - appendOp(4, OpCodes.MATCH_ANY_ANCESTOR); + nextToken(); + + if (tokenIs('/')) + { + appendOp(4, OpCodes.MATCH_ANY_ANCESTOR); + + nextToken(); + } + else + { + appendOp(4, OpCodes.MATCH_IMMEDIATE_ANCESTOR); + } // Tell how long the step is without the predicate m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - 2] = 4; m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - 1] = OpCodes.NODETYPE_FUNCTEST; - nextToken(); - nextToken(); + relativePathStatus = RELATIVE_PATH_REQUIRED; } } else if (tokenIs('/')) @@ -2057,12 +2179,17 @@ // of a '//' pattern, and so will cause 'a' to be matched when it has // any ancestor that is 'x'. nextToken(); + + relativePathStatus = RELATIVE_PATH_REQUIRED; } else { appendOp(4, OpCodes.FROM_ROOT); + + relativePathStatus = RELATIVE_PATH_PERMITTED; } + // Tell how long the step is without the predicate m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - 2] = 4; m_ops.m_opMap[m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - 1] = @@ -2070,10 +2197,22 @@ nextToken(); } + else + { + relativePathStatus = RELATIVE_PATH_REQUIRED; + } - if (!tokenIs('|') && (null != m_token)) + if (relativePathStatus != RELATIVE_PATH_NOT_PERMITTED) { - RelativePathPattern(); + if (!tokenIs('|') && (null != m_token)) + { + RelativePathPattern(); + } + else if (relativePathStatus == RELATIVE_PATH_REQUIRED) + { + // "A relative path pattern was expected." + error(XPATHErrorResources.ER_EXPECTED_REL_PATH_PATTERN, null); + } } // Terminate for safety. @@ -2103,18 +2242,24 @@ * | RelativePathPattern '/' StepPattern * | RelativePathPattern '//' StepPattern * - * * @throws javax.xml.transform.TransformerException */ - protected void RelativePathPattern() throws javax.xml.transform.TransformerException + protected void RelativePathPattern() + throws javax.xml.transform.TransformerException { - StepPattern(); + // Caller will have consumed any '/' or '//' preceding the + // RelativePathPattern, so let StepPattern know it can't begin with a '/' + boolean trailingSlashConsumed = StepPattern(false); while (tokenIs('/')) { nextToken(); - StepPattern(); + + // StepPattern() may consume first slash of pair in "a//b" while + // processing StepPattern "a". On next iteration, let StepPattern know + // that happened, so it doesn't match ill-formed patterns like "a///b". + trailingSlashConsumed = StepPattern(!trailingSlashConsumed); } } @@ -2122,22 +2267,32 @@ * * StepPattern ::= AbbreviatedNodeTestStep * + * @param isLeadingSlashPermitted a boolean indicating whether a slash can + * appear at the start of this step + * + * @return boolean indicating whether a slash following the step was consumed * * @throws javax.xml.transform.TransformerException */ - protected void StepPattern() throws javax.xml.transform.TransformerException + protected boolean StepPattern(boolean isLeadingSlashPermitted) + throws javax.xml.transform.TransformerException { - AbbreviatedNodeTestStep(); + return AbbreviatedNodeTestStep(isLeadingSlashPermitted); } /** * * AbbreviatedNodeTestStep ::= '@'? NodeTest Predicate * + * @param isLeadingSlashPermitted a boolean indicating whether a slash can + * appear at the start of this step + * + * @return boolean indicating whether a slash following the step was consumed * * @throws javax.xml.transform.TransformerException */ - protected void AbbreviatedNodeTestStep() throws javax.xml.transform.TransformerException + protected boolean AbbreviatedNodeTestStep(boolean isLeadingSlashPermitted) + throws javax.xml.transform.TransformerException { int opPos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; @@ -2163,6 +2318,7 @@ } else if (tokenIs("child")) { + matchTypePos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR; appendOp(2, axesType); @@ -2180,6 +2336,11 @@ } else if (tokenIs('/')) { + if (!isLeadingSlashPermitted) + { + // "A step was expected in the pattern, but '/' was encountered." + error(XPATHErrorResources.ER_EXPECTED_STEP_PATTERN, null); + } axesType = OpCodes.MATCH_ANY_ANCESTOR; appendOp(2, axesType); @@ -2187,11 +2348,6 @@ } else { - if (tokenIs('/')) - { - nextToken(); - } - matchTypePos = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH]; axesType = OpCodes.MATCH_IMMEDIATE_ANCESTOR; @@ -2212,15 +2368,37 @@ Predicate(); } + boolean trailingSlashConsumed; + + // For "a//b", where "a" is current step, we need to mark operation of + // current step as "MATCH_ANY_ANCESTOR". Then we'll consume the first + // slash and subsequent step will be treated as a MATCH_IMMEDIATE_ANCESTOR + // (unless it too is followed by '//'.) + // + // %REVIEW% Following is what happens today, but I'm not sure that's + // %REVIEW% correct behaviour. Perhaps no valid case could be constructed + // %REVIEW% where it would matter? + // + // If current step is on the attribute axis (e.g., "@x//b"), we won't + // change the current step, and let following step be marked as + // MATCH_ANY_ANCESTOR on next call instead. if ((matchTypePos > -1) && tokenIs('/') && lookahead('/', 1)) { m_ops.m_opMap[matchTypePos] = OpCodes.MATCH_ANY_ANCESTOR; nextToken(); + + trailingSlashConsumed = true; + } + else + { + trailingSlashConsumed = false; } // Tell how long the entire step is. m_ops.m_opMap[opPos + OpMap.MAPINDEX_LENGTH] = m_ops.m_opMap[OpMap.MAPINDEX_LENGTH] - opPos; + + return trailingSlashConsumed; } } 1.15 +24 -0 xml-xalan/java/src/org/apache/xpath/res/XPATHErrorResources.java Index: XPATHErrorResources.java =================================================================== RCS file: /home/cvs/xml-xalan/java/src/org/apache/xpath/res/XPATHErrorResources.java,v retrieving revision 1.14 retrieving revision 1.15 diff -u -r1.14 -r1.15 --- XPATHErrorResources.java 26 Jun 2002 15:17:38 -0000 1.14 +++ XPATHErrorResources.java 27 Jun 2002 15:01:19 -0000 1.15 @@ -491,6 +491,30 @@ // Programmer's assertion in getNextStepPos: unknown stepType: {0} public static final int ER_UNKNOWN_STEP = 94; + + /** Problem with RelativeLocationPath */ + public static final int ER_EXPECTED_REL_LOC_PATH = 95; + + + /** Problem with LocationPath */ + public static final int ER_EXPECTED_LOC_PATH = 96; + + + /** Problem with Step */ + public static final int ER_EXPECTED_LOC_STEP = 97; + + + /** Problem with NodeTest */ + public static final int ER_EXPECTED_NODE_TEST = 98; + + + /** Expected step pattern */ + public static final int ER_EXPECTED_STEP_PATTERN = 99; + + + /** Expected relative path pattern */ + public static final int ER_EXPECTED_REL_PATH_PATTERN = 100; + 1.5 +12 -0 xml-xalan/java/src/org/apache/xpath/res/XPATHErrorResources.properties Index: XPATHErrorResources.properties =================================================================== RCS file: /home/cvs/xml-xalan/java/src/org/apache/xpath/res/XPATHErrorResources.properties,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- XPATHErrorResources.properties 26 Jun 2002 15:17:38 -0000 1.4 +++ XPATHErrorResources.properties 27 Jun 2002 15:01:19 -0000 1.5 @@ -194,6 +194,18 @@ ER0093={0} only allows {1} arguments # ER_UNKNOWN_STEP ER0094=Programmer's assertion in getNextStepPos: unknown stepType: {0} +# ER_EXPECTED_REL_LOC_PATH +ER0095=A relative location path was expected following the '/' or '//' token. +# ER_EXPECTED_LOC_PATH +ER0096=A location path was expected, but the following token was encountered\u003a {0} +# ER_EXPECTED_LOC_STEP +ER0097=A location step was expected following the '/' or '//' token. +# ER_EXPECTED_NODE_TEST +ER0098=A node test that matches either NCName:* or QName was expected. +# ER_EXPECTED_STEP_PATTERN +ER0099=A step pattern was expected, but '/' was encountered. +# ER_EXPECTED_REL_PATH_PATTERN +ER00100=A relative path pattern was expected.
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]