View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    https://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.api.dsmlv2;
21  
22  
23  import java.nio.charset.StandardCharsets;
24  import java.util.Arrays;
25  import java.util.Base64;
26  import java.util.Collection;
27  
28  import javax.xml.transform.Transformer;
29  import javax.xml.transform.TransformerConfigurationException;
30  import javax.xml.transform.TransformerException;
31  import javax.xml.transform.TransformerFactory;
32  import javax.xml.transform.stream.StreamSource;
33  
34  import org.apache.directory.api.asn1.util.Asn1Buffer;
35  import org.apache.directory.api.dsmlv2.actions.ReadSoapHeader;
36  import org.apache.directory.api.dsmlv2.request.BatchRequestDsml;
37  import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.Processing;
38  import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.ResponseOrder;
39  import org.apache.directory.api.i18n.I18n;
40  import org.apache.directory.api.ldap.codec.api.LdapApiService;
41  import org.apache.directory.api.ldap.model.message.Control;
42  import org.apache.directory.api.util.Strings;
43  import org.dom4j.Document;
44  import org.dom4j.Element;
45  import org.dom4j.Namespace;
46  import org.dom4j.QName;
47  import org.dom4j.io.DocumentResult;
48  import org.dom4j.io.DocumentSource;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  import org.xmlpull.v1.XmlPullParser;
52  import org.xmlpull.v1.XmlPullParserException;
53  
54  
55  /**
56   * This class is a Helper class for the DSML Parser
57   *
58   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
59   */
60  public final class ParserUtils
61  {
62      /** W3C XML Schema URI. */
63      public static final String XML_SCHEMA_URI = "http://www.w3.org/2001/XMLSchema";
64  
65      /** W3C XML Schema Instance URI. */
66      public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.org/2001/XMLSchema-instance";
67  
68      /** Base-64 identifier. */
69      public static final String BASE64BINARY = "base64Binary";
70  
71      /** XSI namespace prefix. */
72      public static final String XSI = "xsi";
73  
74      /** XSD namespace prefix. */
75      public static final String XSD = "xsd";
76  
77      /** XSD namespace prefix with ':'. */
78      public static final String XSD_COLON = "xsd:";
79  
80      /** The DSML namespace */
81      public static final Namespace DSML_NAMESPACE = new Namespace( null, "urn:oasis:names:tc:DSML:2:0:core" );
82  
83      /** The XSD namespace */
84      public static final Namespace XSD_NAMESPACE = new Namespace( XSD, XML_SCHEMA_URI );
85  
86      /** The XSI namespace */
87      public static final Namespace XSI_NAMESPACE = new Namespace( XSI, XML_SCHEMA_INSTANCE_URI );
88  
89      /** A logger for this class */
90      private static final Logger LOG = LoggerFactory.getLogger( ParserUtils.class );
91  
92      /**
93       * GrammarAction that reads the SOAP header data
94       */
95      public static final GrammarAction READ_SOAP_HEADER = new ReadSoapHeader();
96  
97      private ParserUtils()
98      {
99      }
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 }