CPENameBinder.java
/**
* Portions of this software was developed by employees of the National Institute
* of Standards and Technology (NIST), an agency of the Federal Government and is
* being made available as a public service. Pursuant to title 17 United States
* Code Section 105, works of NIST employees are not subject to copyright
* protection in the United States. This software may be subject to foreign
* copyright. Permission in the United States and in foreign countries, to the
* extent that NIST may hold copyright, to use, copy, modify, create derivative
* works, and distribute this software and its documentation without fee is hereby
* granted on a non-exclusive basis, provided that this notice and disclaimer
* of warranty appears in all copies.
*
* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
* EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
* THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
* INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
* SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
* SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
* INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
* OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
* CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
* PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
* OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
*/
// Copyright (c) 2011, The MITRE Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list
// of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// * Neither the name of The MITRE Corporation nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package gov.nist.secauto.cpe.naming;
import gov.nist.secauto.cpe.common.LogicalValue;
import gov.nist.secauto.cpe.common.Utilities;
import gov.nist.secauto.cpe.common.WellFormedName;
/**
* The CPENameBinder class is a simple implementation of the CPE Name binding algorithm, as
* specified in the CPE Naming Standard version 2.3.
*
* @see <a href= "https://doi.org/10.6028/NIST.IR.7695">NISTIR 7695 Section 6.1.2</a>
* @see <a href= "https://doi.org/10.6028/NIST.IR.7695">NISTIR 7695 Section 6.2.2</a>
*
* @author <a href="mailto:jkraunelis@mitre.org">Joshua Kraunelis</a>
* @author <a href="mailto:david.waltermire@nist.gov">David Waltermire</a>
*/
public class CPENameBinder {
private CPENameBinder() {
// disable construction
}
// Define the attributes that correspond to the seven components in a v2.2. CPE.
public static final WellFormedName.Attribute[] URI_ATTRIBUTES
= { WellFormedName.Attribute.PART, WellFormedName.Attribute.VENDOR, WellFormedName.Attribute.PRODUCT,
WellFormedName.Attribute.VERSION, WellFormedName.Attribute.UPDATE, WellFormedName.Attribute.EDITION,
// requires packing
WellFormedName.Attribute.LANGUAGE };
/**
* Binds a {@link WellFormedName} object to a URI.
*
* @param wfn
* WellFormedName to be bound to URI
* @return URI binding of WFN
*/
public static String bindToURI(WellFormedName wfn) {
// Initialize the output with the CPE v2.2 URI prefix.
String uri = "cpe:/";
// Iterate over the well formed name
for (WellFormedName.Attribute attr : URI_ATTRIBUTES) {
String value = "";
if (WellFormedName.Attribute.EDITION.equals(attr)) {
// Call the pack() helper function to compute the proper
// binding for the edition element.
String edition = bindValueForURI(wfn.get(WellFormedName.Attribute.EDITION));
String swEdition = bindValueForURI(wfn.get(WellFormedName.Attribute.SW_EDITION));
String targetSoftware = bindValueForURI(wfn.get(WellFormedName.Attribute.TARGET_SW));
String targetHardware = bindValueForURI(wfn.get(WellFormedName.Attribute.TARGET_HW));
String other = bindValueForURI(wfn.get(WellFormedName.Attribute.OTHER));
value = pack(edition, swEdition, targetSoftware, targetHardware, other);
} else {
// Get the value for attr in wfn, then bind to a string
// for inclusion in the URI.
value = bindValueForURI(wfn.get(attr));
}
// Append value to the URI then add a colon.
uri = Utilities.strcat(uri, value, ":");
}
// Return the URI string, with trailing colons trimmed.
return trim(uri);
}
/**
* Top-level function used to bind WFN w to formatted string.
*
* @param wfn
* WellFormedName to bind
* @return Formatted String
*/
public static String bindToFS(WellFormedName wfn) {
// Initialize the output with the CPE v2.3 string prefix.
String fs = "cpe:2.3:";
for (WellFormedName.Attribute attr : WellFormedName.Attribute.values()) {
String value = bindValueForFS(wfn.get(attr));
fs = Utilities.strcat(fs, value);
// add a colon except at the very end
if (!WellFormedName.Attribute.OTHER.equals(attr)) {
fs = Utilities.strcat(fs, ":");
}
}
return fs;
}
/**
* Convert the value v to its proper string representation for insertion to formatted string.
*
* @param value
* value to convert
* @return Formatted value
*/
private static String bindValueForFS(Object value) {
if (value instanceof LogicalValue) {
LogicalValue logicalValue = (LogicalValue) value;
// The value NA binds to a blank.
if (LogicalValue.ANY.equals(logicalValue)) {
return "*";
}
// The value NA binds to a single hyphen.
if (LogicalValue.NA.equals(logicalValue)) {
return "-";
}
}
return processQuotedChars((String) value);
}
/**
* Inspect each character in the provided string, and escape as required.
* <p>
* Certain non-alpha characters pass thru without escaping into the result, but most retain
* escaping.
*
* @param str
* the string to process
* @return the processed string result
*/
private static String processQuotedChars(String str) {
String result = "";
int index = 0;
while (index < Utilities.strlen(str)) {
String ch = Utilities.substr(str, index, index + 1);
if (!ch.equals("\\")) {
// unquoted characters pass thru unharmed.
result = Utilities.strcat(result, ch);
} else {
// escaped characters are examined.
String nextchr = Utilities.substr(str, index + 1, index + 2);
// the period, hyphen and underscore pass unharmed.
if (nextchr.equals(".") || nextchr.equals("-") || nextchr.equals("_")) {
result = Utilities.strcat(result, nextchr);
index = index + 2;
continue;
} else {
// all others retain escaping.
result = Utilities.strcat(result, "\\", nextchr);
index = index + 2;
continue;
}
}
index = index + 1;
}
return result;
}
/**
* Converts a value to the proper string for including in a CPE v2.2-conformant URI. The logical
* value ANY binds to the blank in the 2.2-conformant URI.
*
* @param value
* the value to be converted
* @return the converted string
*/
private static String bindValueForURI(Object value) {
if (value instanceof LogicalValue) {
LogicalValue logicalValue = (LogicalValue) value;
// The value NA binds to a blank.
if (LogicalValue.ANY.equals(logicalValue)) {
return "";
}
// The value NA binds to a single hyphen.
if (LogicalValue.NA.equals(logicalValue)) {
return "-";
}
}
// If we get here, we're dealing with a string value.
return transformForURI((String) value);
}
/**
* Scans an input string and performs a series of transformations to convert the string to a bound
* URI form.
* <p>
* The following transformations are performed:
* <ul>
* <li>Pass alphanumeric characters thru untouched</li>
* <li>Percent-encode quoted non-alphanumerics as needed</li>
* <li>Unquoted special characters are mapped to their special forms</li>
* </ul>
*
* @param str
* string to be transformed
* @return transformed string
*/
private static String transformForURI(String str) {
String result = "";
int idx = 0;
while (idx < Utilities.strlen(str)) {
// Get the idx'th character of s.
String thischar = Utilities.substr(str, idx, idx + 1);
// Alphanumerics (incl. underscore) pass untouched.
if (Utilities.isAlphanum(thischar)) {
result = Utilities.strcat(result, thischar);
idx = idx + 1;
continue;
}
// Check for escape character.
if (thischar.equals("\\")) {
idx = idx + 1;
String nxtchar = Utilities.substr(str, idx, idx + 1);
result = Utilities.strcat(result, pctEncode(nxtchar));
idx = idx + 1;
continue;
}
// Bind the unquoted '?' special character to "%01".
if (thischar.equals("?")) {
result = Utilities.strcat(result, "%01");
}
// Bind the unquoted '*' special character to "%02".
if (thischar.equals("*")) {
result = Utilities.strcat(result, "%02");
}
idx = idx + 1;
}
return result;
}
/**
* Returns the appropriate percent-encoding of character c. Certain characters are returned without
* encoding.
*
* @param ch
* the single character string to be encoded
* @return the percent encoded string
*/
private static String pctEncode(String ch) {
if (ch.equals("!")) {
return "%21";
}
if (ch.equals("\"")) {
return "%22";
}
if (ch.equals("#")) {
return "%23";
}
if (ch.equals("$")) {
return "%24";
}
if (ch.equals("%")) {
return "%25";
}
if (ch.equals("&")) {
return "%26";
}
if (ch.equals("'")) {
return "%27";
}
if (ch.equals("(")) {
return "%28";
}
if (ch.equals(")")) {
return "%29";
}
if (ch.equals("*")) {
return "%2a";
}
if (ch.equals("+")) {
return "%2b";
}
if (ch.equals(",")) {
return "%2c";
}
// bound without encoding.
if (ch.equals("-")) {
return ch;
}
// bound without encoding.
if (ch.equals(".")) {
return ch;
}
if (ch.equals("/")) {
return "%2f";
}
if (ch.equals(":")) {
return "%3a";
}
if (ch.equals(";")) {
return "%3b";
}
if (ch.equals("<")) {
return "%3c";
}
if (ch.equals("=")) {
return "%3d";
}
if (ch.equals(">")) {
return "%3e";
}
if (ch.equals("?")) {
return "%3f";
}
if (ch.equals("@")) {
return "%40";
}
if (ch.equals("[")) {
return "%5b";
}
if (ch.equals("\\")) {
return "%5c";
}
if (ch.equals("]")) {
return "%5d";
}
if (ch.equals("^")) {
return "%5e";
}
if (ch.equals("`")) {
return "%60";
}
if (ch.equals("{")) {
return "%7b";
}
if (ch.equals("|")) {
return "%7c";
}
if (ch.equals("}")) {
return "%7d";
}
if (ch.equals("~")) {
return "%7d";
}
// Shouldn't reach here, return original character
return ch;
}
/**
* Packs the values of the five arguments into the single edition component. If all the values are
* blank, the function returns a blank.
*
* @param edition
* edition string
* @param swEdition
* software edition string
* @param targetSoftware
* target software string
* @param targetHardware
* target hardware string
* @param other
* other edition information string
* @return the packed string, or blank
*/
private static String pack(String edition, String swEdition, String targetSoftware, String targetHardware,
String other) {
if (swEdition.equals("") && targetSoftware.equals("") && targetHardware.equals("") && other.equals("")) {
// All the extended attributes are blank, so don't do
// any packing, just return ed.
return edition;
}
// Otherwise, pack the five values into a single string
// prefixed and internally delimited with the tilde.
return Utilities.strcat("~", edition, "~", swEdition, "~", targetSoftware, "~", targetHardware, "~", other);
}
/**
* Removes trailing colons from the URI.
*
* @param str
* the string to be trimmed
* @return the trimmed string
*/
private static String trim(String str) {
String s1 = Utilities.reverse(str);
int idx = 0;
for (int i = 0; i != Utilities.strlen(s1); i++) {
if (Utilities.substr(s1, i, i + 1).equals(":")) {
idx = idx + 1;
} else {
break;
}
}
// Return the substring after all trailing colons,
// reversed back to its original character order.
return Utilities.reverse(Utilities.substr(s1, idx, Utilities.strlen(s1)));
}
}