1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.util.xml;
35
36 import fr.paris.lutece.portal.service.util.AppLogService;
37 import fr.paris.lutece.portal.service.util.AppPropertiesService;
38
39 import java.io.StringReader;
40 import java.io.StringWriter;
41
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Map.Entry;
46 import java.util.Properties;
47 import java.util.concurrent.ConcurrentHashMap;
48 import java.util.concurrent.ConcurrentMap;
49
50 import javax.xml.XMLConstants;
51 import javax.xml.transform.Result;
52 import javax.xml.transform.Source;
53 import javax.xml.transform.Templates;
54 import javax.xml.transform.Transformer;
55 import javax.xml.transform.TransformerConfigurationException;
56 import javax.xml.transform.TransformerException;
57 import javax.xml.transform.TransformerFactory;
58 import javax.xml.transform.TransformerFactoryConfigurationError;
59 import javax.xml.transform.stream.StreamResult;
60 import javax.xml.transform.stream.StreamSource;
61
62
63
64
65 public final class XmlTransformer
66 {
67 private static final String ERROR_MESSAGE_XLST = "Error transforming document XSLT : ";
68 public static final String PROPERTY_TRANSFORMER_POOL_SIZE = "service.xmlTransformer.transformerPoolSize";
69 public static final int TRANSFORMER_POOL_SIZE = AppPropertiesService.getPropertyInt( PROPERTY_TRANSFORMER_POOL_SIZE, 2 );
70 public static final int MAX_TRANSFORMER_SIZE = 1000;
71 private static final List<ConcurrentMap<String, Templates>> transformersPoolList = new ArrayList<>( TRANSFORMER_POOL_SIZE );
72
73 static
74 {
75 for ( int i = 0; i < TRANSFORMER_POOL_SIZE; i++ )
76 {
77 transformersPoolList.add( new ConcurrentHashMap<String, Templates>( MAX_TRANSFORMER_SIZE ) );
78 }
79 }
80
81 private static final String ORACLE_ENABLE_EXTENSION_FUNCTIONS = "http://www.oracle.com/xml/jaxp/properties/enableExtensionFunctions";
82 private static final String SAXON_ALLOW_EXTERNAL_FUNCTIONS = "http://saxon.sf.net/feature/allow-external-functions";
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 private TransformerFactory createSecureTransformerFactory( ) throws TransformerConfigurationException
101 {
102 TransformerFactory tf = TransformerFactory.newInstance( );
103 tf.setFeature( XMLConstants.FEATURE_SECURE_PROCESSING, true );
104 disableExtensionFunctions( tf );
105 setExternalAccessRestrictions( tf );
106 tf.setURIResolver( ( href, base ) -> {
107 throw new TransformerException( "External URI resolution blocked: " + href );
108 } );
109 return tf;
110 }
111
112
113
114
115
116
117
118
119
120 private void disableExtensionFunctions( TransformerFactory tf ) throws TransformerConfigurationException
121 {
122 boolean bDisabled = false;
123
124 try
125 {
126 tf.setFeature( ORACLE_ENABLE_EXTENSION_FUNCTIONS, false );
127 bDisabled = true;
128 }
129 catch( TransformerConfigurationException e )
130 {
131 AppLogService.debug( "Oracle enableExtensionFunctions not supported, trying Saxon property" );
132 }
133
134 if ( !bDisabled )
135 {
136 try
137 {
138 tf.setFeature( SAXON_ALLOW_EXTERNAL_FUNCTIONS, false );
139 bDisabled = true;
140 }
141 catch( TransformerConfigurationException e )
142 {
143 AppLogService.debug( "Saxon allow-external-functions not supported either" );
144 }
145 }
146
147 if ( !bDisabled )
148 {
149 throw new TransformerConfigurationException(
150 "Failed to disable XSLT extension functions: neither Oracle/JDK nor Saxon property is supported by " + tf.getClass( ).getName( ) );
151 }
152 }
153
154
155
156
157
158
159
160
161 private void setExternalAccessRestrictions( TransformerFactory tf )
162 {
163 try
164 {
165 tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_DTD, "" );
166 }
167 catch( IllegalArgumentException e )
168 {
169 AppLogService.debug( "ACCESS_EXTERNAL_DTD not supported by {}", tf.getClass( ).getName( ) );
170 }
171
172 try
173 {
174 tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "" );
175 }
176 catch( IllegalArgumentException e )
177 {
178 AppLogService.debug( "ACCESS_EXTERNAL_STYLESHEET not supported by {}", tf.getClass( ).getName( ) );
179 }
180 }
181
182
183
184
185
186
187
188
189
190
191
192
193
194 private Templates getTemplates( Source stylesheet, String strStyleSheetId ) throws TransformerException
195 {
196 Templates result = null;
197
198 if ( TRANSFORMER_POOL_SIZE > 0 )
199 {
200 int nTransformerListIndex = 0;
201
202 do
203 {
204 result = transformersPoolList.get( nTransformerListIndex ).remove( strStyleSheetId );
205 nTransformerListIndex++;
206 }
207 while ( ( result == null ) && ( nTransformerListIndex < TRANSFORMER_POOL_SIZE ) );
208 }
209
210 if ( result == null )
211 {
212
213 try
214 {
215 result = createSecureTransformerFactory( ).newTemplates( stylesheet );
216 AppLogService.debug( " -- XML Templates instantiation : strStyleSheetId= {}", strStyleSheetId );
217 }
218 catch( TransformerConfigurationException e )
219 {
220 String strMessage = e.getMessage( );
221
222 if ( e.getLocationAsString( ) != null )
223 {
224 strMessage += ( "- location : " + e.getLocationAsString( ) );
225 }
226
227 throw new TransformerException( ERROR_MESSAGE_XLST + strMessage, e.getCause( ) );
228 }
229 catch( TransformerFactoryConfigurationError e )
230 {
231 throw new TransformerException( ERROR_MESSAGE_XLST + e.getMessage( ), e );
232 }
233 }
234
235 return result;
236 }
237
238
239
240
241 public static void cleanTransformerList( )
242 {
243 for ( ConcurrentMap<String, Templates> transformerList : transformersPoolList )
244 {
245 transformerList.clear( );
246 }
247 }
248
249
250
251
252
253
254 public static int getTransformersCount( )
255 {
256 int nCount = 0;
257
258 for ( ConcurrentMap<String, Templates> transformerList : transformersPoolList )
259 {
260 nCount += transformerList.size( );
261 }
262
263 return nCount;
264 }
265
266
267
268
269
270
271
272
273
274 private void releaseTemplates( Templates templates, String strStyleSheetId )
275 {
276 if ( TRANSFORMER_POOL_SIZE > 0 )
277 {
278 Templates result = null;
279 ConcurrentMap<String, Templates> transformerList = null;
280 int nTransformerListIndex = 0;
281
282 do
283 {
284 transformerList = transformersPoolList.get( nTransformerListIndex );
285 nTransformerListIndex++;
286
287
288 if ( transformerList.size( ) < MAX_TRANSFORMER_SIZE )
289 {
290 result = transformerList.putIfAbsent( strStyleSheetId, templates );
291 }
292 else
293 {
294
295 transformerList.clear( );
296
297 AppLogService.info( "XmlTransformer : cache is full, you may need to increase cache size." );
298 }
299 }
300 while ( ( result != null ) && ( nTransformerListIndex < TRANSFORMER_POOL_SIZE ) );
301 }
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 public String transform( Source source, Source stylesheet, String strStyleSheetId, Map<String, String> params, Properties outputProperties )
322 throws TransformerException
323 {
324 Templates templates = this.getTemplates( stylesheet, strStyleSheetId );
325 Transformer transformer = templates.newTransformer( );
326
327
328 transformer.setURIResolver( ( href, base ) -> {
329 AppLogService.error( "XSLT security: blocked document() call to external URI: {}", href );
330 return new StreamSource( new StringReader( "<blocked/>" ) );
331 } );
332
333 if ( outputProperties != null )
334 {
335 transformer.setOutputProperties( outputProperties );
336 }
337
338 if ( params != null )
339 {
340 transformer.clearParameters( );
341
342 for ( Entry<String, String> entry : params.entrySet( ) )
343 {
344 transformer.setParameter( entry.getKey( ), entry.getValue( ) );
345 }
346 }
347
348 StringWriter sw = new StringWriter( );
349 Result result = new StreamResult( sw );
350
351 try
352 {
353 transformer.transform( source, result );
354 }
355 catch( TransformerException e )
356 {
357 String strMessage = "strStyleSheetId = " + strStyleSheetId + " " + e.getMessage( );
358
359 if ( e.getLocationAsString( ) != null )
360 {
361 strMessage += ( " - location : " + e.getLocationAsString( ) );
362 }
363
364 throw new TransformerException( ERROR_MESSAGE_XLST + strMessage, e.getCause( ) );
365 }
366 finally
367 {
368 this.releaseTemplates( templates, strStyleSheetId );
369 }
370
371 return sw.toString( );
372 }
373 }