001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.oscal.lib; 028 029import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream; 030 031import gov.nist.secauto.metaschema.model.common.util.ObjectUtils; 032import gov.nist.secauto.oscal.lib.model.BackMatter.Resource; 033import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Base64; 034import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Rlink; 035 036import org.xml.sax.EntityResolver; 037import org.xml.sax.InputSource; 038import org.xml.sax.SAXException; 039 040import java.io.IOException; 041import java.net.URI; 042import java.nio.ByteBuffer; 043import java.util.List; 044import java.util.UUID; 045import java.util.regex.Matcher; 046import java.util.regex.Pattern; 047 048import edu.umd.cs.findbugs.annotations.NonNull; 049import edu.umd.cs.findbugs.annotations.Nullable; 050 051public final class OscalUtils { 052 public static final String OSCAL_VERSION = "1.0.4"; 053 private static final Pattern INTERNAL_REFERENCE_FRAGMENT_PATTERN = Pattern.compile("^#(.+)$"); 054 055 private OscalUtils() { 056 // disable construction 057 } 058 059 @SuppressWarnings("PMD.OnlyOneReturn") // readability 060 public static boolean isInternalReference(@NonNull URI uri) { 061 if (uri.isAbsolute()) { 062 return false; 063 } 064 065 String schemeSpecificPart = uri.getSchemeSpecificPart(); 066 return uri.getScheme() == null && (schemeSpecificPart == null || schemeSpecificPart.isEmpty()) 067 && uri.getFragment() != null; 068 } 069 070 /** 071 * Get the id based on a URI's fragment. 072 * 073 * @param fragment 074 * the URI to extract the identifier from 075 * @return the identifier 076 * @throws IllegalArgumentException 077 * if the fragment does not contain an identifier 078 */ 079 @NonNull 080 public static String internalReferenceFragmentToId(@NonNull URI fragment) { 081 return internalReferenceFragmentToId(ObjectUtils.notNull(fragment.toString())); 082 } 083 084 /** 085 * Get the id based on a URI's fragment. 086 * 087 * @param fragment 088 * the URI to extract the identifier from 089 * @return the identifier 090 * @throws IllegalArgumentException 091 * if the fragment does not contain an identifier 092 */ 093 @NonNull 094 public static String internalReferenceFragmentToId(@NonNull String fragment) { 095 Matcher matcher = INTERNAL_REFERENCE_FRAGMENT_PATTERN.matcher(fragment); 096 String retval; 097 if (matcher.matches()) { 098 retval = ObjectUtils.notNull(matcher.group(1)); 099 } else { 100 throw new IllegalArgumentException(String.format("The fragment '%s' does not match the pattern '%s'", fragment, 101 INTERNAL_REFERENCE_FRAGMENT_PATTERN.pattern())); 102 } 103 return retval; 104 } 105 106 public static boolean hasBase64Data(@NonNull Resource resource) { 107 return resource.getBase64() != null; 108 } 109 110 @Nullable 111 public static ByteBuffer getBase64Data(@NonNull Resource resource) { 112 Base64 base64 = resource.getBase64(); 113 114 ByteBuffer retval = null; 115 if (base64 != null) { 116 retval = base64.getValue(); 117 } 118 return retval; 119 } 120 121 @Nullable 122 public static URI getResourceURI(@NonNull Resource resource, @Nullable String preferredMediaType) { 123 URI retval; 124 if (hasBase64Data(resource)) { 125 UUID uuid = resource.getUuid(); 126 if (uuid == null) { 127 throw new IllegalArgumentException("resource has a null UUID"); 128 } 129 retval = ObjectUtils.notNull(URI.create("#" + uuid)); 130 } else { 131 Rlink rlink = findMatchingRLink(resource, preferredMediaType); 132 retval = rlink == null ? null : rlink.getHref(); 133 } 134 return retval; 135 } 136 137 @Nullable 138 public static Rlink findMatchingRLink(@NonNull Resource resource, @Nullable String preferredMediaType) { 139 // find a suitable rlink reference 140 List<Rlink> rlinks = resource.getRlinks(); 141 142 Rlink retval = null; 143 if (rlinks != null) { 144 // check if there is a matching rlink for the mime type 145 if (preferredMediaType != null) { 146 // find preferred mime type first 147 retval = rlinks.stream().filter(rlink -> preferredMediaType.equals(rlink.getMediaType())).findFirst() 148 .orElse(null); 149 } else { 150 // use the first one instead 151 retval = rlinks.stream().findFirst().orElse(null); 152 } 153 } 154 return retval; 155 } 156 157 @Nullable 158 public static InputSource newInputSource(@NonNull Resource resource, @NonNull EntityResolver resolver, 159 @Nullable String preferredMediaType) throws IOException { 160 URI uri = getResourceURI(resource, preferredMediaType); 161 if (uri == null) { 162 throw new IOException(String.format("unable to determine URI for resource '%s'", resource.getUuid())); 163 } 164 165 InputSource retval; 166 try { 167 retval = resolver.resolveEntity(null, uri.toASCIIString()); 168 } catch (SAXException ex) { 169 throw new IOException(ex); 170 } 171 172 if (hasBase64Data(resource)) { 173 // handle base64 encoded data 174 ByteBuffer buffer = getBase64Data(resource); 175 if (buffer == null) { 176 throw new IOException(String.format("null base64 value for resource '%s'", resource.getUuid())); 177 } 178 retval.setByteStream(new ByteBufferBackedInputStream(buffer)); 179 } 180 return retval; 181 } 182}