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