001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.dsmlv2; 021 022 023import java.nio.charset.StandardCharsets; 024import java.util.Arrays; 025import java.util.Base64; 026import java.util.Collection; 027 028import javax.xml.transform.Transformer; 029import javax.xml.transform.TransformerConfigurationException; 030import javax.xml.transform.TransformerException; 031import javax.xml.transform.TransformerFactory; 032import javax.xml.transform.stream.StreamSource; 033 034import org.apache.directory.api.asn1.util.Asn1Buffer; 035import org.apache.directory.api.dsmlv2.actions.ReadSoapHeader; 036import org.apache.directory.api.dsmlv2.request.BatchRequestDsml; 037import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.Processing; 038import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.ResponseOrder; 039import org.apache.directory.api.i18n.I18n; 040import org.apache.directory.api.ldap.codec.api.LdapApiService; 041import org.apache.directory.api.ldap.model.message.Control; 042import org.apache.directory.api.util.Strings; 043import org.dom4j.Document; 044import org.dom4j.Element; 045import org.dom4j.Namespace; 046import org.dom4j.QName; 047import org.dom4j.io.DocumentResult; 048import org.dom4j.io.DocumentSource; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051import org.xmlpull.v1.XmlPullParser; 052import org.xmlpull.v1.XmlPullParserException; 053 054 055/** 056 * This class is a Helper class for the DSML Parser 057 * 058 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 059 */ 060public final class ParserUtils 061{ 062 /** W3C XML Schema URI. */ 063 public static final String XML_SCHEMA_URI = "http://www.w3.org/2001/XMLSchema"; 064 065 /** W3C XML Schema Instance URI. */ 066 public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.org/2001/XMLSchema-instance"; 067 068 /** Base-64 identifier. */ 069 public static final String BASE64BINARY = "base64Binary"; 070 071 /** XSI namespace prefix. */ 072 public static final String XSI = "xsi"; 073 074 /** XSD namespace prefix. */ 075 public static final String XSD = "xsd"; 076 077 /** XSD namespace prefix with ':'. */ 078 public static final String XSD_COLON = "xsd:"; 079 080 /** The DSML namespace */ 081 public static final Namespace DSML_NAMESPACE = new Namespace( null, "urn:oasis:names:tc:DSML:2:0:core" ); 082 083 /** The XSD namespace */ 084 public static final Namespace XSD_NAMESPACE = new Namespace( XSD, XML_SCHEMA_URI ); 085 086 /** The XSI namespace */ 087 public static final Namespace XSI_NAMESPACE = new Namespace( XSI, XML_SCHEMA_INSTANCE_URI ); 088 089 /** A logger for this class */ 090 private static final Logger LOG = LoggerFactory.getLogger( ParserUtils.class ); 091 092 /** 093 * GrammarAction that reads the SOAP header data 094 */ 095 public static final GrammarAction READ_SOAP_HEADER = new ReadSoapHeader(); 096 097 private ParserUtils() 098 { 099 } 100 101 102 /** 103 * Returns the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists 104 * 105 * @param xpp the XPP parser to use 106 * @return the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists 107 */ 108 public static String getXsiTypeAttributeValue( XmlPullParser xpp ) 109 { 110 String type = null; 111 int nbAttributes = xpp.getAttributeCount(); 112 113 for ( int i = 0; i < nbAttributes; i++ ) 114 { 115 // Checking if the attribute 'type' from XML Schema Instance namespace is used. 116 if ( DsmlLiterals.TYPE.equals( xpp.getAttributeName( i ) ) 117 && xpp.getNamespace( xpp.getAttributePrefix( i ) ).equals( XML_SCHEMA_INSTANCE_URI ) ) 118 { 119 type = xpp.getAttributeValue( i ); 120 break; 121 } 122 } 123 124 return type; 125 } 126 127 128 /** 129 * Tells is the given value is a Base64 binary value 130 * 131 * @param parser the XPP parser to use 132 * @param attrValue the attribute value 133 * @return true if the value of the current tag is Base64BinaryEncoded, false if not 134 */ 135 public static boolean isBase64BinaryValue( XmlPullParser parser, String attrValue ) 136 { 137 if ( attrValue == null ) 138 { 139 return false; 140 } 141 142 // We are looking for something that should look like that: "aNameSpace:base64Binary" 143 // We split the String. The first element should be the namespace prefix and the second "base64Binary" 144 String[] splitedString = attrValue.split( ":" ); 145 146 return ( splitedString.length == 2 ) && ( XML_SCHEMA_URI.equals( parser.getNamespace( splitedString[0] ) ) ) 147 && ( BASE64BINARY.equals( splitedString[1] ) ); 148 } 149 150 151 /** 152 * Indicates if the value needs to be encoded as Base64 153 * 154 * @param value the value to check 155 * @return true if the value needs to be encoded as Base64 156 */ 157 public static boolean needsBase64Encoding( Object value ) 158 { 159 return ( value instanceof byte[] ); 160 } 161 162 163 /** 164 * Encodes the value as a Base64 String 165 * 166 * @param value the value to encode 167 * @return the value encoded as a Base64 String 168 */ 169 public static String base64Encode( Object value ) 170 { 171 if ( value instanceof byte[] ) 172 { 173 return new String( Base64.getEncoder().encode( ( byte[] ) value ), StandardCharsets.UTF_8 ); 174 } 175 else if ( value instanceof String ) 176 { 177 return new String( Base64.getEncoder().encode( Strings.getBytesUtf8( ( String ) value ) ), StandardCharsets.UTF_8 ); 178 } 179 180 return Strings.EMPTY_STRING; 181 } 182 183 184 /** 185 * Parses and verify the parsed value of the requestID 186 * 187 * @param attributeValue the value of the attribute 188 * @param xpp the XmlPullParser 189 * @return the int value of the resquestID 190 * @throws XmlPullParserException if RequestID isn't an Integer and if requestID is below 0 191 */ 192 public static int parseAndVerifyRequestID( String attributeValue, XmlPullParser xpp ) throws XmlPullParserException 193 { 194 try 195 { 196 int requestID = Integer.parseInt( attributeValue ); 197 198 if ( requestID < 0 ) 199 { 200 throw new XmlPullParserException( I18n.err( I18n.ERR_03016_BELOW_0_REQUEST_ID, requestID ), xpp, null ); 201 } 202 203 return requestID; 204 } 205 catch ( NumberFormatException nfe ) 206 { 207 throw new XmlPullParserException( I18n.err( I18n.ERR_03012_REQUEST_ID_NOT_INTEGER ), xpp, nfe ); 208 } 209 } 210 211 212 /** 213 * Adds Controls to the given Element. 214 * 215 * @param codec The LDAP Service to use 216 * @param element the element to add the Controls to 217 * @param controls a List of Controls 218 * @param isRequest A flag set to <tt>true</tt> if teh LDapMessage is a request 219 */ 220 public static void addControls( LdapApiService codec, Element element, Collection<Control> controls, boolean isRequest ) 221 { 222 if ( controls != null ) 223 { 224 for ( Control control : controls ) 225 { 226 Element controlElement = element.addElement( DsmlLiterals.CONTROL ); 227 228 if ( control.getOid() != null ) 229 { 230 controlElement.addAttribute( DsmlLiterals.TYPE, control.getOid() ); 231 } 232 233 if ( control.isCritical() ) 234 { 235 controlElement.addAttribute( DsmlLiterals.CRITICALITY, DsmlLiterals.TRUE ); 236 } 237 238 Asn1Buffer asn1Buffer = new Asn1Buffer(); 239 240 if ( isRequest ) 241 { 242 codec.getRequestControlFactories().get( control.getOid() ).encodeValue( asn1Buffer, control ); 243 } 244 else 245 { 246 codec.getResponseControlFactories().get( control.getOid() ).encodeValue( asn1Buffer, control ); 247 } 248 249 byte[] value = asn1Buffer.getBytes().array(); 250 251 if ( value != null ) 252 { 253 if ( ParserUtils.needsBase64Encoding( value ) ) 254 { 255 element.getDocument().getRootElement().add( XSD_NAMESPACE ); 256 element.getDocument().getRootElement().add( XSI_NAMESPACE ); 257 258 Element valueElement = controlElement.addElement( DsmlLiterals.CONTROL_VALUE ).addText( 259 ParserUtils.base64Encode( value ) ); 260 valueElement.addAttribute( new QName( DsmlLiterals.TYPE, XSI_NAMESPACE ), ParserUtils.XSD_COLON 261 + ParserUtils.BASE64BINARY ); 262 } 263 else 264 { 265 controlElement.addElement( DsmlLiterals.CONTROL_VALUE ).setText( Arrays.toString( value ) ); 266 } 267 } 268 } 269 } 270 } 271 272 273 /** 274 * Indicates if a request ID is needed. 275 * 276 * @param container the associated container 277 * @return true if a request ID is needed (ie Processing=Parallel and ResponseOrder=Unordered) 278 * @throws XmlPullParserException if the batch request has not been parsed yet 279 */ 280 public static boolean isRequestIdNeeded( Dsmlv2Container container ) throws XmlPullParserException 281 { 282 BatchRequestDsml batchRequest = container.getBatchRequest(); 283 284 if ( batchRequest == null ) 285 { 286 throw new XmlPullParserException( I18n.err( I18n.ERR_03003_UNABLE_TO_FIND_BATCH_REQUEST ), container.getParser(), null ); 287 } 288 289 return ( batchRequest.getProcessing() == Processing.PARALLEL ) && ( batchRequest.getResponseOrder() == ResponseOrder.UNORDERED ); 290 } 291 292 293 /** 294 * XML Pretty Printer XSLT Transformation 295 * 296 * @param document the Dom4j Document 297 * @return the transformed document 298 */ 299 public static Document styleDocument( Document document ) 300 { 301 // load the transformer using JAXP 302 TransformerFactory factory = TransformerFactory.newInstance(); 303 Transformer transformer = null; 304 305 try 306 { 307 factory.setFeature( javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE ); 308 try 309 { 310 factory.setAttribute( javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, Strings.EMPTY_STRING ); 311 factory.setAttribute( javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET, Strings.EMPTY_STRING ); 312 } 313 catch ( IllegalArgumentException ex ) 314 { 315 // ignore 316 } 317 transformer = factory.newTransformer( new StreamSource( ParserUtils.class 318 .getResourceAsStream( "/org/apache/directory/shared/dsmlv2/DSMLv2.xslt" ) ) ); 319 } 320 catch ( TransformerConfigurationException e1 ) 321 { 322 if ( LOG.isWarnEnabled() ) 323 { 324 LOG.warn( I18n.msg( I18n.MSG_3000_FAILED_TO_CREATE_XSLT_TRANSFORMER ), e1 ); 325 } 326 327 // return original document 328 return document; 329 } 330 331 // now lets style the given document 332 DocumentSource source = new DocumentSource( document ); 333 DocumentResult result = new DocumentResult(); 334 335 try 336 { 337 transformer.transform( source, result ); 338 } 339 catch ( TransformerException e ) 340 { 341 // return original document 342 return document; 343 } 344 345 // return the transformed document 346 return result.getDocument(); 347 } 348}