View Javadoc
1   /**
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  // Copyright (c) 2011, The MITRE Corporation
27  
28  // All rights reserved.
29  //
30  // Redistribution and use in source and binary forms, with or without modification, are
31  // permitted provided that the following conditions are met:
32  //
33  //    * Redistributions of source code must retain the above copyright notice, this list
34  //      of conditions and the following disclaimer.
35  //    * Redistributions in binary form must reproduce the above copyright notice, this
36  //      list of conditions and the following disclaimer in the documentation and/or other
37  //      materials provided with the distribution.
38  //    * Neither the name of The MITRE Corporation nor the names of its contributors may be
39  //      used to endorse or promote products derived from this software without specific
40  //      prior written permission.
41  //
42  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
43  // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
44  // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
45  // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
47  // OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
49  // TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
50  // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51  
52  package gov.nist.secauto.cpe.matching;
53  
54  import gov.nist.secauto.cpe.common.LogicalValue;
55  import gov.nist.secauto.cpe.common.Utilities;
56  import gov.nist.secauto.cpe.common.WellFormedName;
57  import gov.nist.secauto.cpe.common.WellFormedName.Attribute;
58  
59  import java.util.LinkedHashMap;
60  import java.util.Map;
61  
62  /**
63   * The CPENameMatcher is an implementation of the CPE Matching algorithm, as specified in the CPE
64   * Matching Standard version 2.3.
65   * 
66   * @see <a href= "https://doi.org/10.6028/NIST.IR.7696">NISTIR 7696 Section 7</a>
67   * 
68   * @author <a href="mailto:jkraunelis@mitre.org">Joshua Kraunelis</a>
69   * @author <a href="mailto:david.waltermire@nist.gov">David Waltermire</a>
70   */
71  public class CPENameMatcher {
72    private CPENameMatcher() {
73      // disable construction
74    }
75  
76    /**
77     * Tests two Well Formed Names for disjointness.
78     * 
79     * @param source
80     *          Source WFN
81     * @param target
82     *          Target WFN
83     * @return true if the names are disjoint, false otherwise
84     */
85    public static boolean isDisjoint(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) {
86      // if any pairwise comparison is disjoint, the names are disjoint.
87      Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target);
88      for (Relation result : resultList.values()) {
89        if (Relation.DISJOINT.equals(result)) {
90          return true;
91        }
92      }
93      return false;
94    }
95  
96    /**
97     * Tests two Well Formed Names for equality.
98     * 
99     * @param source
100    *          Source WFN
101    * @param target
102    *          Target WFN
103    * @return true if the names are equal, false otherwise
104    */
105   public static boolean isEqual(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) {
106     // if every pairwise comparison is equal, the names are equal.
107     Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target);
108     for (Relation result : resultList.values()) {
109       if (!(Relation.EQUAL.equals(result))) {
110         return false;
111       }
112     }
113     return true;
114   }
115 
116   /**
117    * Tests if the target Well Formed Name is a subset of the source Well Formed Name.
118    * 
119    * @param source
120    *          Source WFN
121    * @param target
122    *          Target WFN
123    * @return true if the target is a subset of the source, false otherwise
124    */
125   public static boolean isSubset(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) {
126     // if any comparison is anything other than subset or equal, then target is
127     // not a subset of source.
128     Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target);
129     for (Relation result : resultList.values()) {
130       if (!(Relation.SUBSET.equals(result)) && !(Relation.EQUAL.equals(result))) {
131         return false;
132       }
133     }
134     return true;
135   }
136 
137   /**
138    * Tests if the target Well Formed name is a superset of the source Well Formed Name.
139    * 
140    * @param source
141    *          Source WFN
142    * @param target
143    *          Target WFN
144    * @return true if the target is a superset of the source, false otherwise
145    */
146   public static boolean isSuperset(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) {
147     // if any comparison is anything other than superset or equal, then target is
148     // not
149     // a superset of source.
150     Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target);
151     for (Relation result : resultList.values()) {
152       if ((!Relation.SUPERSET.equals(result)) && (!Relation.EQUAL.equals(result))) {
153         return false;
154       }
155     }
156     return true;
157   }
158 
159   /**
160    * Compares each attribute value pair in two Well Formed Names.
161    * 
162    * @param source
163    *          Source WFN
164    * @param target
165    *          Target WFN
166    * @return A Hashtable mapping attribute string to attribute value Relation
167    */
168   public static Map<WellFormedName.Attribute, Relation> compareWFNs(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) {
169     Map<WellFormedName.Attribute, Relation> result
170         = new LinkedHashMap<WellFormedName.Attribute, Relation>(Attribute.values().length);
171     for (WellFormedName.Attribute attribute : Attribute.values()) {
172       result.put(attribute, compare(source.get(attribute), target.get(attribute)));
173     }
174     return result;
175   }
176 
177   /**
178    * Compares an attribute value pair.
179    * 
180    * @param source
181    *          Source attribute value.
182    * @param target
183    *          Target attribute value.
184    * @return The relation between the two attribute values.
185    */
186   private static Relation compare(Object source, Object target) {
187     // matching is case insensitive, convert strings to lowercase.
188     if (isString(source)) {
189       source = Utilities.toLowercase((String) source);
190     }
191     if (isString(target)) {
192       target = Utilities.toLowercase((String) target);
193     }
194 
195     // Unquoted wildcard characters yield an undefined result.
196     if (isString(target) && Utilities.containsWildcards((String) target)) {
197       return Relation.UNDEFINED;
198     }
199     // If source and target values are equal, then result is equal.
200     if (source.equals(target)) {
201       return Relation.EQUAL;
202     }
203 
204     // Check to see if source or target are Logical Values.
205     LogicalValue lvSource = null;
206     LogicalValue lvTarget = null;
207     if (source instanceof LogicalValue) {
208       lvSource = (LogicalValue) source;
209     }
210     if (target instanceof LogicalValue) {
211       lvTarget = (LogicalValue) target;
212     }
213 
214     // If Logical Values are equal, result is equal.
215     if (lvSource != null && lvTarget != null && lvSource.equals(lvTarget)) {
216       return Relation.EQUAL;
217     }
218 
219     // If source value is ANY, result is a superset.
220     if (lvSource != null && LogicalValue.ANY.equals(lvSource)) {
221       return Relation.SUPERSET;
222     }
223     // If target value is ANY, result is a subset.
224     if (lvTarget != null && LogicalValue.ANY.equals(lvTarget)) {
225       return Relation.SUBSET;
226     }
227     // If source or target is NA, result is disjoint.
228     if (lvSource != null && LogicalValue.NA.equals(lvSource)) {
229       return Relation.DISJOINT;
230     }
231 
232     if (lvTarget != null && LogicalValue.NA.equals(lvTarget)) {
233       return Relation.DISJOINT;
234     }
235     // only Strings will get to this point, not LogicalValues
236     return compareStrings((String) source, (String) target);
237   }
238 
239   /**
240    * Compares a source string to a target string, and addresses the condition in which the source
241    * string includes unquoted special characters. It performs a simple regular expression match, with
242    * the assumption that (as required) unquoted special characters appear only at the beginning and/or
243    * the end of the source string. It also properly differentiates between unquoted and quoted special
244    * characters.
245    * 
246    * @return Relation between source and target Strings.
247    */
248   private static Relation compareStrings(String source, String target) {
249     int start = 0;
250     int end = Utilities.strlen(source);
251     int begins = 0;
252     int ends = 0;
253 
254     if (Utilities.substr(source, 0, 1).equals("*")) {
255       start = 1;
256       begins = -1;
257     } else {
258       while ((start < Utilities.strlen(source)) && (Utilities.substr(source, start, start + 1).equals("?"))) {
259         start = start + 1;
260         begins = begins + 1;
261       }
262     }
263     if ((Utilities.substr(source, end - 1, end).equals("*")) && (isEvenWildcards(source, end - 1))) {
264       end = end - 1;
265       ends = -1;
266     } else {
267       while ((end > 0) && Utilities.substr(source, end - 1, end).equals("?") && (isEvenWildcards(source, end - 1))) {
268         end = end - 1;
269         ends = ends + 1;
270       }
271     }
272 
273     source = Utilities.substr(source, start, end);
274 
275     int index = -1;
276     int leftover = Utilities.strlen(target);
277     Relation retval = Relation.DISJOINT;
278     while (leftover > 0) {
279       index = Utilities.indexOf(target, source, index + 1);
280       if (index == -1) {
281         break;
282       }
283       int escapes = Utilities.countEscapeCharacters(target, 0, index);
284       if ((index > 0) && (begins != -1) && (begins < (index - escapes))) {
285         break;
286       }
287       escapes = Utilities.countEscapeCharacters(target, index + 1, Utilities.strlen(target));
288       leftover = Utilities.strlen(target) - index - escapes - Utilities.strlen(source);
289       if ((leftover > 0) && ((ends != -1) && (leftover > ends))) {
290         continue;
291       } else {
292         retval = Relation.SUPERSET;
293         break;
294       }
295     }
296     return retval;
297   }
298 
299   /**
300    * Searches a string for the backslash character.
301    * 
302    * @param str
303    *          string to search in
304    * @param idx
305    *          end index
306    * @return true if the number of backslash characters is even, false if odd
307    */
308   private static boolean isEvenWildcards(String str, int idx) {
309     int result = 0;
310     while ((idx > 0) && (Utilities.strchr(str, '\\', idx - 1)) != -1) {
311       idx = idx - 1;
312       result = result + 1;
313     }
314     return Utilities.isEvenNumber(result);
315   }
316 
317   /**
318    * Tests if an Object is an instance of the String class.
319    * 
320    * @param arg
321    *          the Object to test
322    * @return true if arg is a String, false if not
323    */
324   private static boolean isString(Object arg) {
325     if (arg instanceof String) {
326       return true;
327     } else {
328       return false;
329     }
330   }
331 }