Source code: org/apache/batik/bridge/ExternalResourcesTest.java
1 /*
2
3 Copyright 2002-2003 The Apache Software Foundation
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 */
18 package org.apache.batik.bridge;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24 import java.util.StringTokenizer;
25 import java.util.Vector;
26
27 import org.w3c.dom.Document;
28 import org.w3c.dom.Element;
29 import org.w3c.dom.Node;
30
31 import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
32 import org.apache.batik.gvt.GraphicsNode;
33 import org.apache.batik.test.AbstractTest;
34 import org.apache.batik.test.DefaultTestReport;
35 import org.apache.batik.test.TestReport;
36 import org.apache.batik.util.ParsedURL;
37 import org.apache.batik.util.XMLResourceDescriptor;
38
39 /**
40 * This test validates that SecurityExceptions are generated when
41 * the user is trying the access external resources and the UserAgent
42 * disallows that.
43 *
44 * In the following, 'unsecure' means an external resource coming from
45 * a different location than the file referencing it.
46 *
47 * This test works with an SVG file containing an unsecure stylesheet
48 * and a set of unsecure elements of all kinds, such as <image>
49 * <use> or <feImage>. All these elements are defined
50 * in a defs section. The test tries to load the document and validates
51 * that a SecurityException is thrown (because of the unsecure
52 * stylesheet). Then, the test iterates over the various unsecure
53 * elements, inserting them into the document outside the defs
54 * section, which should result in a SecurityException in each case.
55 *
56 * There is a property (secure) to have the test work the opposite
57 * way and check that no SecurityException is thrown if access
58 * to external resources is allowed.
59 *
60 * @author <a href="mailto:vhardy@apache.org">Vincent Hardy</a>
61 * @version $Id: ExternalResourcesTest.java,v 1.6 2004/08/18 07:16:38 vhardy Exp $
62 */
63
64 public class ExternalResourcesTest extends AbstractTest
65 implements ErrorConstants {
66 /**
67 * Error when the input file cannot be loaded into a
68 * Document object
69 * {0} = IOException message
70 */
71 public static final String ERROR_CANNOT_LOAD_SVG_DOCUMENT
72 = "ExternalResourcesTest.error.cannot.load.svg.document";
73
74 /**
75 * Error while processing the document
76 * {0} = BridgeException message
77 */
78 public static final String ERROR_WHILE_PROCESSING_SVG_DOCUMENT
79 = "ExternalResourcesTest.error.while.processing.svg.document";
80
81 /**
82 * Error: an expected exception was not thrown
83 * {0} = List of ids for which the exception was not thrown
84 */
85 public static final String ERROR_UNTHROWN_SECURITY_EXCEPTIONS
86 = "ExternalResourcesTest.error.unthrown.security.exceptions";
87
88 /**
89 * Error: an unexpected exception was thrown
90 * {0} = List of ids for which an exception was thrown
91 */
92 public static final String ERROR_THROWN_SECURITY_EXCEPTIONS
93 = "ExternalResourcesTest.error.thrown.security.exceptions";
94
95 /**
96 * Error when the insertion point cannot be found in the
97 * test document
98 * {0} = insertion point id
99 */
100 public static final String ERROR_NO_INSERTION_POINT_IN_DOCUMENT
101 = "ExternalResourceTest.error.no.insertion.point.in.document";
102
103 /**
104 * Error when the test could not find a list of ids for testing
105 */
106 public static final String ERROR_NO_ID_LIST
107 = "ExternalResourceTest.error.no.id.list";
108
109 /**
110 * Error when one of the target id cannot be found
111 * {0} = id which was not found
112 */
113 public static final String ERROR_TARGET_ID_NOT_FOUND
114 = "ExternalResourcesTest.error.target.id.not.found";
115
116 /**
117 * Entry describing the error
118 */
119 public static final String ENTRY_KEY_ERROR_DESCRIPTION
120 = "ExternalResourcesTest.entry.key.error.description";
121
122 public static final String ENTRY_KEY_INSERTION_POINT_ID
123 = "ExternalResourcesTest.entry.key.insertion.point.id";
124
125 public static final String ENTRY_KEY_TARGET_ID
126 = "ExternalResourcesTest.entry.target.id";
127
128 public static final String ENTRY_KEY_EXPECTED_EXCEPTION_ON
129 = "ExternalResourcesTest.entry.key.expected.exception.on";
130
131 public static final String ENTRY_KEY_UNEXPECTED_EXCEPTION_ON
132 = "ExternalResourcesTest.entry.key.unexpected.exception.on";
133
134 /**
135 * Pseudo id for the external stylesheet test
136 */
137 public static final String EXTERNAL_STYLESHEET_ID
138 = "external-stylesheet";
139
140 /**
141 * Test Namespace
142 */
143 public static final String testNS = "http://xml.apache.org/batik/test";
144
145 /**
146 * Id of the element where unsecure content is inserted
147 */
148 public static final String INSERTION_POINT_ID = "insertionPoint";
149
150 /**
151 * Location of test files in filesystem.
152 */
153 public static final String FILE_DIR =
154 "test-resources/org/apache/batik/bridge/";
155 /**
156 * Controls whether the test works in secure mode or not
157 */
158 protected boolean secure = true;
159
160 String svgURL;
161
162 public void setId(String id){
163 super.setId(id);
164 String file = id;
165 int idx = file.indexOf('.');
166 if (idx != -1) {
167 file = file.substring(0,idx);
168 }
169 svgURL = resolveURL(FILE_DIR + file + ".svg");
170 }
171
172 public Boolean getSecure(){
173 return new Boolean(secure);
174 }
175
176 public void setSecure(Boolean secure) {
177 this.secure = secure.booleanValue();
178 }
179
180 /**
181 * Resolves the input string as follows.
182 * + First, the string is interpreted as a file description.
183 * If the file exists, then the file name is turned into
184 * a URL.
185 * + Otherwise, the string is supposed to be a URL. If it
186 * is an invalid URL, an IllegalArgumentException is thrown.
187 */
188 protected String resolveURL(String url){
189 // Is url a file?
190 File f = (new File(url)).getAbsoluteFile();
191 if(f.getParentFile().exists()){
192 try{
193 return f.toURL().toString();
194 }catch(MalformedURLException e){
195 throw new IllegalArgumentException();
196 }
197 }
198
199 // url is not a file. It must be a regular URL...
200 try{
201 return (new URL(url)).toString();
202 }catch(MalformedURLException e){
203 throw new IllegalArgumentException(url);
204 }
205 }
206
207 /**
208 * This test uses a list of ids found in the test document. These ids reference
209 * elements found in a defs section. For each such element, the test will
210 * attempt to insert the target id at a given insertion point. That insertion
211 * should cause a SecurityException. If so, the test passes. Otherwise, the test
212 * will fail
213 */
214 public TestReport runImpl() throws Exception{
215 DefaultTestReport report
216 = new DefaultTestReport(this);
217
218 //
219 // First step:
220 //
221 // Load the input SVG into a Document object
222 //
223 String parserClassName = XMLResourceDescriptor.getXMLParserClassName();
224 SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parserClassName);
225 Document doc = null;
226
227 try {
228 doc = f.createDocument(svgURL);
229 } catch(IOException e){
230 report.setErrorCode(ERROR_CANNOT_LOAD_SVG_DOCUMENT);
231 report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION,
232 e.getMessage());
233 report.setPassed(false);
234 return report;
235 } catch(Exception e){
236 report.setErrorCode(ERROR_CANNOT_LOAD_SVG_DOCUMENT);
237 report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION,
238 e.getMessage());
239 report.setPassed(false);
240 return report;
241 }
242
243 Vector failures = new Vector();
244
245 //
246 // Do an initial processing to validate that the external
247 // stylesheet causes a SecurityException
248 //
249 MyUserAgent userAgent = buildUserAgent();
250 GVTBuilder builder = new GVTBuilder();
251 BridgeContext ctx = new BridgeContext(userAgent);
252 ctx.setDynamic(true);
253
254 // We expect either a SecurityException or a BridgeException
255 // with ERR_URI_UNSECURE.
256 Throwable th = null;
257 try {
258 GraphicsNode gn = builder.build(ctx, doc);
259 gn.getBounds();
260 th = userAgent.getDisplayError();
261 } catch (BridgeException e){
262 th = e;
263 } catch (SecurityException e) {
264 th = e;
265 } catch (Throwable t) {
266 th = t;
267 }
268 if (th == null) {
269 if (secure)
270 failures.addElement(EXTERNAL_STYLESHEET_ID);
271 } else if (th instanceof SecurityException) {
272 if (!secure)
273 failures.addElement(EXTERNAL_STYLESHEET_ID);
274 } else if (th instanceof BridgeException) {
275 BridgeException be = (BridgeException)th;
276 if (!secure ||
277 (secure && !ERR_URI_UNSECURE.equals(be.getCode()))) {
278 report.setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT);
279 report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION,
280 be.getMessage());
281 report.setPassed(false);
282 return report;
283 }
284 }
285
286 //
287 // Remove the stylesheet from the document
288 //
289 Node child = doc.getFirstChild();
290 Node next = null;
291 while (child != null) {
292 next = child.getNextSibling();
293 if (child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
294 doc.removeChild(child);
295 }
296 child = next;
297 }
298
299 //
300 // Now, get the list of ids to be checked
301 //
302 Element root = doc.getDocumentElement();
303 String idList = root.getAttributeNS(testNS, "targetids");
304 if (idList == null || "".equals(idList)) {
305 report.setErrorCode(ERROR_NO_ID_LIST);
306 report.setPassed(false);
307 return report;
308 }
309
310 StringTokenizer st = new StringTokenizer(idList, ",");
311 String[] ids = new String[st.countTokens()];
312 for (int i=0; i<ids.length; i++) {
313 ids[i] = st.nextToken().toString().trim();
314 }
315
316 for (int i=0; i<ids.length; i++) {
317 String id = ids[i];
318 userAgent = buildUserAgent();
319 builder = new GVTBuilder();
320 ctx = new BridgeContext(userAgent);
321 ctx.setDynamic(true);
322
323 Document cloneDoc = (Document)doc.cloneNode(true);
324 Element insertionPoint = cloneDoc.getElementById(INSERTION_POINT_ID);
325
326 if (insertionPoint == null) {
327 report.setErrorCode(ERROR_NO_INSERTION_POINT_IN_DOCUMENT);
328 report.addDescriptionEntry(ENTRY_KEY_INSERTION_POINT_ID,
329 INSERTION_POINT_ID);
330 report.setPassed(false);
331 return report;
332 }
333
334 Element target = cloneDoc.getElementById(id);
335
336 if (target == null) {
337 report.setErrorCode(ERROR_TARGET_ID_NOT_FOUND);
338 report.addDescriptionEntry(ENTRY_KEY_TARGET_ID,
339 id);
340 report.setPassed(false);
341 return report;
342 }
343
344 insertionPoint.appendChild(target);
345 th = null;
346 try {
347 GraphicsNode gn = builder.build(ctx, cloneDoc);
348 gn.getBounds();
349 th = userAgent.getDisplayError();
350 } catch (BridgeException e){
351 th = e;
352 } catch (SecurityException e) {
353 th = e;
354 } catch (Throwable t) {
355 th = t;
356 }
357 if (th == null) {
358 if (secure)
359 failures.addElement(id);
360 } else if (th instanceof SecurityException) {
361 if (!secure)
362 failures.addElement(id);
363 } else if (th instanceof BridgeException) {
364 BridgeException be = (BridgeException)th;
365 if (!secure ||
366 (secure && !ERR_URI_UNSECURE.equals(be.getCode()))) {
367 report.setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT);
368 report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION,
369 be.getMessage());
370 report.setPassed(false);
371 return report;
372 }
373 } else {
374 // Some unknown exception was displayed...
375 report.setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT);
376 report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION,
377 th.getMessage());
378 report.setPassed(false);
379 return report;
380 }
381
382 }
383
384 if (failures.size() == 0) {
385 return reportSuccess();
386 }
387
388 if (secure) {
389 report.setErrorCode(ERROR_UNTHROWN_SECURITY_EXCEPTIONS);
390 for (int i=0; i<failures.size(); i++) {
391 report.addDescriptionEntry(ENTRY_KEY_EXPECTED_EXCEPTION_ON,
392 failures.elementAt(i));
393 }
394 } else {
395 report.setErrorCode(ERROR_THROWN_SECURITY_EXCEPTIONS);
396 for (int i=0; i<failures.size(); i++) {
397 report.addDescriptionEntry(ENTRY_KEY_UNEXPECTED_EXCEPTION_ON,
398 failures.elementAt(i));
399 }
400 }
401
402 report.setPassed(false);
403 return report;
404 }
405
406 protected interface MyUserAgent extends UserAgent {
407 public Exception getDisplayError();
408 }
409
410 protected MyUserAgent buildUserAgent(){
411 if (secure) {
412 return new SecureUserAgent();
413 } else {
414 return new RelaxedUserAgent();
415 }
416 }
417
418 class MyUserAgentAdapter extends UserAgentAdapter implements MyUserAgent {
419 Exception ex = null;
420 public void displayError(Exception ex) {
421 this.ex = ex;
422 super.displayError(ex);
423 }
424
425 public Exception getDisplayError() { return ex; }
426 }
427
428 class SecureUserAgent extends MyUserAgentAdapter {
429 public ExternalResourceSecurity
430 getExternalResourceSecurity(ParsedURL resourcePURL,
431 ParsedURL docPURL){
432 return new NoLoadExternalResourceSecurity();
433
434 }
435 }
436
437 class RelaxedUserAgent extends MyUserAgentAdapter {
438 public ExternalResourceSecurity
439 getExternalResourceSecurity(ParsedURL resourcePURL,
440 ParsedURL docPURL){
441 return new RelaxedExternalResourceSecurity(resourcePURL,
442 docPURL);
443
444 }
445 }
446
447 }