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.naming;
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  
58  import java.text.ParseException;
59  
60  /**
61   * The CPENameUnBinder class is a simple implementation of the CPE Name unbinding algorithm, as
62   * specified in the CPE Naming Standard version 2.3.
63   * 
64   * @see <a href= "https://doi.org/10.6028/NIST.IR.7695">NISTIR 7695 Section 6.1.3</a>
65   * @see <a href= "https://doi.org/10.6028/NIST.IR.7695">NISTIR 7695 Section 6.2.3</a>
66   * 
67   * @author <a href="mailto:jkraunelis@mitre.org">Joshua Kraunelis</a>
68   * @author <a href="mailto:david.waltermire@nist.gov">David Waltermire</a>
69   */
70  public class CPENameUnbinder {
71  
72    private CPENameUnbinder() {
73      // disable construction
74    }
75  
76    /**
77     * Top level function used to unbind a URI to a WFN.
78     * 
79     * @param uri
80     *          String representing the URI to be unbound
81     * @return WellFormedName representing the unbound URI
82     * @throws ParseException
83     *           if the provided uri is invalid
84     */
85    public static WellFormedName unbindURI(String uri) throws ParseException {
86      // Validate the URI
87      Utilities.validateURI(uri);
88      // Initialize the empty WFN.
89      WellFormedNameormedName.html#WellFormedName">WellFormedName result = new WellFormedName();
90  
91      for (int i = 0; i != 8; i++) {
92        // get the i'th component of uri
93        String value = getCompURI(uri, i);
94        if (i > 0) {
95          // Get the WFN component using the enum ordinal
96          WellFormedName.Attribute attribute = WellFormedName.Attribute.values()[i - 1];
97  
98          if (WellFormedName.Attribute.EDITION.equals(attribute)) {
99            // Special handling for edition component.
100           // Unpack edition if needed.
101           if (value.equals("") || value.equals("-") || !Utilities.substr(value, 0, 1).equals("~")) {
102             // Just a logical value or a non-packed value.
103             // So unbind to legacy edition, leaving other
104             // extended attributes unspecified.
105             result.set(attribute, decode(value));
106           } else {
107             // We have five values packed together here.
108             unpack(value, result);
109           }
110         } else {
111           result.set(attribute, decode(value));
112         }
113       }
114     }
115     return result;
116   }
117 
118   /**
119    * Top level function to unbind a formatted string to WFN.
120    * 
121    * @param fs
122    *          Formatted string to unbind
123    * @return WellFormedName representing the unbound formatted string
124    * @throws ParseException
125    *           if the fs argument is malformed
126    */
127   public static WellFormedName unbindFS(String fs) throws ParseException {
128     // Validate the formatted string
129     Utilities.validateFS(fs);
130     // Initialize empty WFN
131     WellFormedNameormedName.html#WellFormedName">WellFormedName result = new WellFormedName();
132     // The cpe scheme is the 0th component, the cpe version is the 1st.
133     // So we start parsing at the 2nd component.
134     for (int a = 2; a != 13; a++) {
135       // Get the a'th string field.
136       Object value = getCompFS(fs, a);
137       // Unbind the string.
138       value = unbindValueFS((String) value);
139 
140       // Get the WFN component using the enum ordinal
141       WellFormedName.Attribute attribute = WellFormedName.Attribute.values()[a - 2];
142 
143       // Set the value of the corresponding attribute.
144       result.set(attribute, value);
145     }
146     return result;
147   }
148 
149   /**
150    * Returns the i'th field of the formatted string. The colon is the field delimiter unless prefixed
151    * by a backslash.
152    * 
153    * @param fs
154    *          formatted string to retrieve from
155    * @param index
156    *          index of field to retrieve from fs.
157    * @return value of index of formatted string
158    */
159   private static String getCompFS(String fs, int index) {
160     if (index == 0) {
161       // return the substring from index 0 to the first occurence of an
162       // unescaped colon
163       int colonIndex = Utilities.getUnescapedColonIndex(fs);
164       // If no colon is found, we are at the end of the formatted string,
165       // so just return what's left.
166       if (colonIndex == 0) {
167         return fs;
168       }
169       return Utilities.substr(fs, 0, colonIndex);
170     } else {
171       return getCompFS(Utilities.substr(fs, Utilities.getUnescapedColonIndex(fs) + 1, fs.length()), index - 1);
172     }
173   }
174 
175   /**
176    * Takes a string value and returns the appropriate logical value if string is the bound form of a
177    * logical value. If string is some general value string, add quoting of non-alphanumerics as
178    * needed.
179    * 
180    * @param value
181    *          value to be unbound
182    * @return logical value or quoted string
183    * @throws ParseException
184    *           if the s argument is malformed
185    */
186   private static Object unbindValueFS(String value) throws ParseException {
187     if (value.equals("*")) {
188       return LogicalValue.ANY;
189     }
190     if (value.equals("-")) {
191       return LogicalValue.NA;
192     }
193     return addQuoting(value);
194   }
195 
196   /**
197    * Inspect each character in a string, copying quoted characters, with their escaping, into the
198    * result. Look for unquoted non alphanumerics and if not "*" or "?", add escaping.
199    * 
200    * @param str
201    *          the string to process
202    * @return a string that has been properly escaped
203    * @throws ParseException
204    *           if the s argument is malformed
205    */
206   private static String addQuoting(String str) throws ParseException {
207     String result = "";
208     int idx = 0;
209     boolean embedded = false;
210     while (idx < Utilities.strlen(str)) {
211       String ch = Utilities.substr(str, idx, idx + 1);
212       if (Utilities.isAlphanum(ch) || ch.equals("_")) {
213         // Alphanumeric characters pass untouched.
214         result = Utilities.strcat(result, ch);
215         idx = idx + 1;
216         embedded = true;
217         continue;
218       }
219       if (ch.equals("\\")) {
220         // Anything quoted in the bound string stays quoted in the
221         // unbound string.
222         result = Utilities.strcat(result, Utilities.substr(str, idx, idx + 2));
223         idx = idx + 2;
224         embedded = true;
225         continue;
226       }
227       if (ch.equals("*")) {
228         // An unquoted asterisk must appear at the beginning or the end
229         // of the string.
230         if (idx == 0 || idx == (Utilities.strlen(str) - 1)) {
231           result = Utilities.strcat(result, ch);
232           idx = idx + 1;
233           embedded = true;
234           continue;
235         } else {
236           throw new ParseException("Error! cannot have unquoted * embedded in formatted string.", 0);
237         }
238       }
239       if (ch.equals("?")) {
240         // An unquoted question mark must appear at the beginning or
241         // end of the string, or in a leading or trailing sequence.
242         // if embedded is false, so must be preceded by ?
243         // if embedded is true, so must be followed by ?
244         if (((idx == 0) || (idx == (Utilities.strlen(str) - 1)))
245             || (!embedded && (Utilities.substr(str, idx - 1, idx).equals("?")))
246             || (embedded && (Utilities.substr(str, idx + 1, idx + 2).equals("?")))) {
247           result = Utilities.strcat(result, ch);
248           idx = idx + 1;
249           embedded = false;
250           continue;
251         } else {
252           throw new ParseException("Error! cannot have unquoted ? embedded in formatted string.", 0);
253         }
254       }
255       // All other characters must be quoted.
256       result = Utilities.strcat(result, "\\", ch);
257       idx = idx + 1;
258       embedded = true;
259     }
260     return result;
261   }
262 
263   /**
264    * Return the i'th component of the URI.
265    * 
266    * @param uri
267    *          String representation of URI to retrieve components from
268    * @param index
269    *          Index of component to return
270    * @return If i = 0, returns the URI scheme. Otherwise, returns the i'th component of uri
271    */
272   private static String getCompURI(String uri, int index) {
273     if (index == 0) {
274       return Utilities.substr(uri, index, uri.indexOf("/"));
275     }
276     String[] sa = uri.split(":");
277     // If requested component exceeds the number
278     // of components in URI, return blank
279     if (index >= sa.length) {
280       return "";
281     }
282     if (index == 1) {
283       return Utilities.substr(sa[index], 1, sa[index].length());
284     }
285     return sa[index];
286   }
287 
288   /**
289    * Scans a string and returns a copy with all percent-encoded characters decoded. This function is
290    * the inverse of pctEncode() defined in the CPENameBinder class. Only legal percent-encoded forms
291    * are decoded. Others raise a ParseException.
292    * 
293    * @param str
294    *          String to be decoded
295    * @return decoded string
296    * @throws ParseException
297    *           if the provided string is invalid
298    * @see CPENameBinder#pctEncode(java.lang.String)
299    */
300   private static Object decode(String str) throws ParseException {
301     if (str.equals("")) {
302       return LogicalValue.ANY;
303     }
304     if (str.equals("-")) {
305       return LogicalValue.NA;
306     }
307     // Start the scanning loop.
308     // Normalize: convert all uppercase letters to lowercase first.
309     str = Utilities.toLowercase(str);
310     String result = "";
311     int idx = 0;
312     boolean embedded = false;
313     while (idx < Utilities.strlen(str)) {
314       // Get the idx'th character of s.
315       String ch = Utilities.substr(str, idx, idx + 1);
316       // Deal with dot, hyphen, and tilde: decode with quoting.
317       if (ch.equals(".") || ch.equals("-") || ch.equals("~")) {
318         result = Utilities.strcat(result, "\\", ch);
319         idx = idx + 1;
320         // a non-%01 encountered.
321         embedded = true;
322         continue;
323       }
324       if (!ch.equals("%")) {
325         result = Utilities.strcat(result, ch);
326         idx = idx + 1;
327         // a non-%01 encountered.
328         embedded = true;
329         continue;
330       }
331       // We get here if we have a substring starting w/ '%'.
332       String form = Utilities.substr(str, idx, idx + 3);
333       if (form.equals("%01")) {
334         if ((idx == 0) || (idx == Utilities.strlen(str) - 3)
335             || (!embedded && Utilities.substr(str, idx - 3, idx - 1).equals("%01"))
336             || (embedded && (Utilities.strlen(str) >= idx + 6))
337                 && (Utilities.substr(str, idx + 3, idx + 6).equals("%01"))) {
338           result = Utilities.strcat(result, "?");
339           idx = idx + 3;
340           continue;
341         } else {
342           throw new ParseException("Error decoding string", 0);
343         }
344       } else if (form.equals("%02")) {
345         if ((idx == 0) || (idx == (Utilities.strlen(str) - 3))) {
346           result = Utilities.strcat(result, "*");
347         } else {
348           throw new ParseException("Error decoding string", 0);
349         }
350       } else if (form.equals("%21")) {
351         result = Utilities.strcat(result, "\\!");
352       } else if (form.equals("%22")) {
353         result = Utilities.strcat(result, "\\\"");
354       } else if (form.equals("%23")) {
355         result = Utilities.strcat(result, "\\#");
356       } else if (form.equals("%24")) {
357         result = Utilities.strcat(result, "\\$");
358       } else if (form.equals("%25")) {
359         result = Utilities.strcat(result, "\\%");
360       } else if (form.equals("%26")) {
361         result = Utilities.strcat(result, "\\&");
362       } else if (form.equals("%27")) {
363         result = Utilities.strcat(result, "\\'");
364       } else if (form.equals("%28")) {
365         result = Utilities.strcat(result, "\\(");
366       } else if (form.equals("%29")) {
367         result = Utilities.strcat(result, "\\)");
368       } else if (form.equals("%2a")) {
369         result = Utilities.strcat(result, "\\*");
370       } else if (form.equals("%2b")) {
371         result = Utilities.strcat(result, "\\+");
372       } else if (form.equals("%2c")) {
373         result = Utilities.strcat(result, "\\,");
374       } else if (form.equals("%2f")) {
375         result = Utilities.strcat(result, "\\/");
376       } else if (form.equals("%3a")) {
377         result = Utilities.strcat(result, "\\)");
378       } else if (form.equals("%3b")) {
379         result = Utilities.strcat(result, "\\;");
380       } else if (form.equals("%3c")) {
381         result = Utilities.strcat(result, "\\<");
382       } else if (form.equals("%3d")) {
383         result = Utilities.strcat(result, "\\=");
384       } else if (form.equals("%3e")) {
385         result = Utilities.strcat(result, "\\>");
386       } else if (form.equals("%3f")) {
387         result = Utilities.strcat(result, "\\?");
388       } else if (form.equals("%40")) {
389         result = Utilities.strcat(result, "\\@");
390       } else if (form.equals("%5b")) {
391         result = Utilities.strcat(result, "\\[");
392       } else if (form.equals("%5c")) {
393         result = Utilities.strcat(result, "\\\\");
394       } else if (form.equals("%5d")) {
395         result = Utilities.strcat(result, "\\]");
396       } else if (form.equals("%5e")) {
397         result = Utilities.strcat(result, "\\^");
398       } else if (form.equals("%60")) {
399         result = Utilities.strcat(result, "\\`");
400       } else if (form.equals("%7b")) {
401         result = Utilities.strcat(result, "\\{");
402       } else if (form.equals("%7c")) {
403         result = Utilities.strcat(result, "\\|");
404       } else if (form.equals("%7d")) {
405         result = Utilities.strcat(result, "\\}");
406       } else if (form.equals("%7e")) {
407         result = Utilities.strcat(result, "\\~");
408       } else {
409         throw new ParseException("Unknown form: " + form, 0);
410       }
411       idx = idx + 3;
412       embedded = true;
413     }
414     return result;
415   }
416 
417   /**
418    * Unpacks the elements in s and sets the attributes in the given WellFormedName accordingly.
419    * 
420    * @param str
421    *          packed String
422    * @param wfn
423    *          WellFormedName
424    * @return The augmented WellFormedName
425    * @throws ParseException
426    *           if the str is marformed
427    */
428   private static WellFormedNamev/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName unpack(String str, WellFormedName wfn) throws ParseException {
429     // Parse out the five elements.
430     int start = 1;
431     int end = Utilities.strchr(str, '~', start);
432     String edition;
433     if (start == end) {
434       edition = "";
435     } else {
436       edition = Utilities.substr(str, start, end);
437     }
438 
439     start = end + 1;
440     end = Utilities.strchr(str, '~', start);
441     String swEdition;
442     if (start == end) {
443       swEdition = "";
444     } else {
445       swEdition = Utilities.substr(str, start, end);
446     }
447 
448     start = end + 1;
449     end = Utilities.strchr(str, '~', start);
450     String targetSoftware;
451     if (start == end) {
452       targetSoftware = "";
453     } else {
454       targetSoftware = Utilities.substr(str, start, end);
455     }
456 
457     start = end + 1;
458     end = Utilities.strchr(str, '~', start);
459     String targetHardware;
460     if (start == end) {
461       targetHardware = "";
462     } else {
463       targetHardware = Utilities.substr(str, start, end);
464     }
465 
466     start = end + 1;
467     String other;
468     if (start >= Utilities.strlen(str)) {
469       other = "";
470     } else {
471       other = Utilities.substr(str, start, Utilities.strlen(str) - 1);
472     }
473 
474     // Set each component in the WFN.
475     wfn.set(WellFormedName.Attribute.EDITION, decode(edition));
476     wfn.set(WellFormedName.Attribute.SW_EDITION, decode(swEdition));
477     wfn.set(WellFormedName.Attribute.TARGET_SW, decode(targetSoftware));
478     wfn.set(WellFormedName.Attribute.TARGET_HW, decode(targetHardware));
479     wfn.set(WellFormedName.Attribute.OTHER, decode(other));
480     return wfn;
481   }
482 }