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.common;
53  
54  import java.text.ParseException;
55  
56  /**
57   * A collection of utility functions for use with the gov.nist.secauto.cpe.matching and
58   * gov.nist.secauto.cpe.naming packages.
59   * 
60   * @see <a href=
61   *      "https://csrc.nist.gov/projects/security-content-automation-protocol/specifications/cpe/">CPE
62   *      Specifications</a>
63   * 
64   * @author <a href="mailto:jkraunelis@mitre.org">Joshua Kraunelis</a>
65   * @author <a href="mailto:david.waltermire@nist.gov">David Waltermire</a>
66   */
67  public class Utilities {
68  
69    /**
70     * Concatenates an arbitrary number of strings, in the given order.
71     * 
72     * @param strings
73     *          strings to be concatenated
74     * @return a single string representing all the arguments, concatenated
75     */
76    public static String strcat(String... strings) {
77      StringBuilder retval = new StringBuilder();
78      for (String s : strings) {
79        retval.append(s);
80      }
81      return retval.toString();
82    }
83  
84    /**
85     * Extracts the characters between b and e, from the string s.
86     * 
87     * @param str
88     *          the string which the substring should be extracted from
89     * @param begin
90     *          beginning index, inclusive
91     * @param end
92     *          ending index, exclusive
93     * @return the characters between index begin and index end
94     */
95    public static String substr(String str, int begin, int end) {
96      return str.substring(begin, end);
97    }
98  
99    /**
100    * Returns the number of characters in the given string.
101    * 
102    * @param str
103    *          the string
104    * @return the number of characters in the string
105    */
106   public static int strlen(String str) {
107     return str.length();
108   }
109 
110   /**
111    * Searches a string for a character starting at a given offset. Returns the offset of the character
112    * if found, -1 if not found.
113    * 
114    * @param str
115    *          string to be searched
116    * @param chr
117    *          character to search for
118    * @param offset
119    *          offset to start at
120    * @return the integer offset of the character, or -1
121    */
122   public static int strchr(String str, int chr, int offset) {
123     return str.indexOf(chr, offset);
124   }
125 
126   /**
127    * Converts all alphabetic characters in a String to lowercase.
128    * 
129    * @param str
130    *          string to convert to lowercase
131    * @return lowercase version of str
132    */
133   public static String toLowercase(String str) {
134     return str.toLowerCase();
135   }
136 
137   /**
138    * Searches a string for the first occurrence of another string, starting at a given offset.
139    * 
140    * @param str1
141    *          String to search.
142    * @param str2
143    *          String to search for.
144    * @param offset
145    *          Integer offset or -1 if not found.
146    * @return the position of the first occurrence of str2, or -1 if the string was not found
147    */
148   public static int indexOf(String str1, String str2, int offset) {
149     return str1.indexOf(str2, offset);
150   }
151 
152   /**
153    * Searches string for special characters * and ?.
154    * 
155    * @param str
156    *          String to be searched
157    * @return {@code true} if string contains a wildcard, or {@code false} otherwise
158    */
159   public static boolean containsWildcards(String str) {
160     if (str.contains("*") || str.contains("?")) {
161       if (!(str.contains("\\"))) {
162         return true;
163       }
164       return false;
165     }
166     return false;
167   }
168 
169   /**
170    * Checks if given number is even or not.
171    * 
172    * @param num
173    *          number to check
174    * @return {@code true} if number is even, {@code false} if not
175    */
176   public static boolean isEvenNumber(int num) {
177     if (num % 2 == 0) {
178       return true;
179     } else {
180       return false;
181     }
182   }
183 
184   /**
185    * Counts the number of escape characters in the string beginning and ending at the given indices.
186    * 
187    * @param str
188    *          string to search
189    * @param start
190    *          beginning index, inclusive
191    * @param end
192    *          ending index, exclusive
193    * @return number of escape characters in string
194    */
195   public static int countEscapeCharacters(String str, int start, int end) {
196     int result = 0;
197     boolean active = false;
198     int pos = 0;
199     while (pos < end) {
200       active = !active && str.charAt(pos) == '\\';
201       if (active && (pos >= start)) {
202         result = result + 1;
203       }
204       pos = pos + 1;
205     }
206     return result;
207   }
208 
209   /**
210    * Searches a string for the first unescaped colon and returns the index of that colon.
211    * 
212    * @param str
213    *          string to search
214    * @return index of first unescaped colon, or 0 if not found
215    */
216   public static int getUnescapedColonIndex(String str) {
217     boolean found = false;
218     int colonIndex = 0;
219     int startIndex = 0;
220     // Find the first non-escaped colon.
221     while (!found) {
222       colonIndex = str.indexOf(':', startIndex + 1);
223       // If no colon is found, return 0.
224       if (colonIndex == -1) {
225         return 0;
226       }
227       // Peek at character before colon.
228       if ((str.substring(colonIndex - 1, colonIndex)).equals("\\")) {
229         // If colon is escaped, keep looking.
230         startIndex = colonIndex;
231       } else {
232         found = true;
233       }
234     }
235     return colonIndex;
236   }
237 
238   /**
239    * Determines if the string contains only alphanumeric characters or the underscore character.
240    * 
241    * @param str
242    *          the string in question
243    * @return {@code true} if c is alphanumeric or underscore, or {@code false} otherwise
244    */
245   public static boolean isAlphanum(String str) {
246     if (str.matches("[a-zA-Z0-9]") || str.equals("_")) {
247       return true;
248     }
249     return false;
250   }
251 
252   /**
253    * Returns a copy of the given string in reverse order.
254    * 
255    * @param str
256    *          the string to be reversed
257    * @return a reversed copy of s
258    */
259   public static String reverse(String str) {
260     return new StringBuffer(str).reverse().toString();
261   }
262 
263   /**
264    * This function is not part of the reference implementation pseudo code found in the CPE 2.3
265    * specification. It enforces two rules in the specification: 1) a CPE URI must start with the
266    * characters "cpe:/". 2) A URI may not contain more than 7 components. If either rule is violated,
267    * a ParseException is thrown.
268    * 
269    * @param str
270    *          the potential CPE formatted string to validate
271    * @throws ParseException
272    *           if one of the rules for a CPE URI is violated
273    */
274   public static void validateURI(String str) throws ParseException {
275     // make sure uri starts with cpe:/
276     if (!str.toLowerCase().startsWith("cpe:/")) {
277       throw new ParseException("Error: URI must start with 'cpe:/'.  Given: " + str, 0);
278     }
279     // make sure uri doesn't contain more than 7 colons
280     int count = 0;
281     for (int i = 0; i != str.length(); i++) {
282       if (str.charAt(i) == ':') {
283         count++;
284       }
285     }
286     if (count > 7) {
287       throw new ParseException("Error parsing URI.  Found " + (count - 7) + " extra components in: " + str, 0);
288     }
289   }
290 
291   /**
292    * This function is not part of the reference implementation pseudo code found in the CPE 2.3
293    * specification. It enforces three rules found in the specification: 1) A CPE formatted string must
294    * start with the characters "cpe:2.3:". 2) A formatted string must contain 11 components. 3) A
295    * formatted string must not contain empty components. If any rule is violated, a ParseException is
296    * thrown.
297    * 
298    * @param str
299    *          the potential CPE formatted string to validate
300    * @throws ParseException
301    *           if one of the rules for a CPE formatted string is violated
302    */
303   public static void validateFS(String str) throws ParseException {
304     if (!str.toLowerCase().startsWith("cpe:2.3:")) {
305       throw new ParseException("Error: Formatted String must start with \"cpe:2.3\". Given: " + str, 0);
306     }
307     // make sure fs contains exactly 12 unquoted colons
308     int count = 0;
309     for (int i = 0; i != str.length(); i++) {
310       if (str.charAt(i) == ':') {
311         if (str.charAt(i - 1) != '\\') {
312           count++;
313         }
314         if ((i + 1) < str.length() && str.charAt(i + 1) == ':') {
315           throw new ParseException("Error parsing formatted string.  Found empty component", 0);
316         }
317       }
318     }
319     if (count > 12) {
320       int extra = (count - 12);
321       StringBuffer msg = new StringBuffer("Error parsing formatted string.  Found " + extra + " extra component");
322       if (extra > 1) {
323         msg.append("s");
324       }
325       msg.append(" in: " + str);
326       throw new ParseException(msg.toString(), 0);
327     }
328     if (count < 12) {
329       int missing = (12 - count);
330       StringBuffer msg = new StringBuffer("Error parsing formatted string. Missing " + missing + " component");
331       if (missing > 1) {
332         msg.append("s");
333       }
334       throw new ParseException(msg.toString(), 0);
335     }
336   }
337 }