001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.bridge;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.net.MalformedURLException;
024: import java.net.URL;
025: import java.util.StringTokenizer;
026: import java.util.List;
027: import java.util.ArrayList;
028:
029: import org.w3c.dom.Document;
030: import org.w3c.dom.Element;
031: import org.w3c.dom.Node;
032:
033: import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
034: import org.apache.batik.gvt.GraphicsNode;
035: import org.apache.batik.test.AbstractTest;
036: import org.apache.batik.test.DefaultTestReport;
037: import org.apache.batik.test.TestReport;
038: import org.apache.batik.util.ParsedURL;
039: import org.apache.batik.util.XMLResourceDescriptor;
040:
041: /**
042: * This test validates that SecurityExceptions are generated when
043: * the user is trying the access external resources and the UserAgent
044: * disallows that.
045: *
046: * In the following, 'unsecure' means an external resource coming from
047: * a different location than the file referencing it.
048: *
049: * This test works with an SVG file containing an unsecure stylesheet
050: * and a set of unsecure elements of all kinds, such as <image>
051: * <use> or <feImage>. All these elements are defined
052: * in a defs section. The test tries to load the document and validates
053: * that a SecurityException is thrown (because of the unsecure
054: * stylesheet). Then, the test iterates over the various unsecure
055: * elements, inserting them into the document outside the defs
056: * section, which should result in a SecurityException in each case.
057: *
058: * There is a property (secure) to have the test work the opposite
059: * way and check that no SecurityException is thrown if access
060: * to external resources is allowed.
061: *
062: * @author <a href="mailto:vhardy@apache.org">Vincent Hardy</a>
063: * @version $Id: ExternalResourcesTest.java 489226 2006-12-21 00:05:36Z cam $
064: */
065:
066: public class ExternalResourcesTest extends AbstractTest implements
067: ErrorConstants {
068: /**
069: * Error when the input file cannot be loaded into a
070: * Document object
071: * {0} = IOException message
072: */
073: public static final String ERROR_CANNOT_LOAD_SVG_DOCUMENT = "ExternalResourcesTest.error.cannot.load.svg.document";
074:
075: /**
076: * Error while processing the document
077: * {0} = BridgeException message
078: */
079: public static final String ERROR_WHILE_PROCESSING_SVG_DOCUMENT = "ExternalResourcesTest.error.while.processing.svg.document";
080:
081: /**
082: * Error: an expected exception was not thrown
083: * {0} = List of ids for which the exception was not thrown
084: */
085: public static final String ERROR_UNTHROWN_SECURITY_EXCEPTIONS = "ExternalResourcesTest.error.unthrown.security.exceptions";
086:
087: /**
088: * Error: an unexpected exception was thrown
089: * {0} = List of ids for which an exception was thrown
090: */
091: public static final String ERROR_THROWN_SECURITY_EXCEPTIONS = "ExternalResourcesTest.error.thrown.security.exceptions";
092:
093: /**
094: * Error when the insertion point cannot be found in the
095: * test document
096: * {0} = insertion point id
097: */
098: public static final String ERROR_NO_INSERTION_POINT_IN_DOCUMENT = "ExternalResourceTest.error.no.insertion.point.in.document";
099:
100: /**
101: * Error when the test could not find a list of ids for testing
102: */
103: public static final String ERROR_NO_ID_LIST = "ExternalResourceTest.error.no.id.list";
104:
105: /**
106: * Error when one of the target id cannot be found
107: * {0} = id which was not found
108: */
109: public static final String ERROR_TARGET_ID_NOT_FOUND = "ExternalResourcesTest.error.target.id.not.found";
110:
111: /**
112: * Entry describing the error
113: */
114: public static final String ENTRY_KEY_ERROR_DESCRIPTION = "ExternalResourcesTest.entry.key.error.description";
115:
116: public static final String ENTRY_KEY_INSERTION_POINT_ID = "ExternalResourcesTest.entry.key.insertion.point.id";
117:
118: public static final String ENTRY_KEY_TARGET_ID = "ExternalResourcesTest.entry.target.id";
119:
120: public static final String ENTRY_KEY_EXPECTED_EXCEPTION_ON = "ExternalResourcesTest.entry.key.expected.exception.on";
121:
122: public static final String ENTRY_KEY_UNEXPECTED_EXCEPTION_ON = "ExternalResourcesTest.entry.key.unexpected.exception.on";
123:
124: /**
125: * Pseudo id for the external stylesheet test
126: */
127: public static final String EXTERNAL_STYLESHEET_ID = "external-stylesheet";
128:
129: /**
130: * Test Namespace
131: */
132: public static final String testNS = "http://xml.apache.org/batik/test";
133:
134: /**
135: * Id of the element where unsecure content is inserted
136: */
137: public static final String INSERTION_POINT_ID = "insertionPoint";
138:
139: /**
140: * Location of test files in filesystem.
141: */
142: public static final String FILE_DIR = "test-resources/org/apache/batik/bridge/";
143: /**
144: * Controls whether the test works in secure mode or not
145: */
146: protected boolean secure = true;
147:
148: String svgURL;
149:
150: public void setId(String id) {
151: super .setId(id);
152: String file = id;
153: int idx = file.indexOf('.');
154: if (idx != -1) {
155: file = file.substring(0, idx);
156: }
157: svgURL = resolveURL(FILE_DIR + file + ".svg");
158: }
159:
160: public Boolean getSecure() {
161: return secure ? Boolean.TRUE : Boolean.FALSE;
162: }
163:
164: public void setSecure(Boolean secure) {
165: this .secure = secure.booleanValue();
166: }
167:
168: /**
169: * Resolves the input string as follows.
170: * + First, the string is interpreted as a file description.
171: * If the file exists, then the file name is turned into
172: * a URL.
173: * + Otherwise, the string is supposed to be a URL. If it
174: * is an invalid URL, an IllegalArgumentException is thrown.
175: */
176: protected String resolveURL(String url) {
177: // Is url a file?
178: File f = (new File(url)).getAbsoluteFile();
179: if (f.getParentFile().exists()) {
180: try {
181: return f.toURL().toString();
182: } catch (MalformedURLException e) {
183: throw new IllegalArgumentException();
184: }
185: }
186:
187: // url is not a file. It must be a regular URL...
188: try {
189: return (new URL(url)).toString();
190: } catch (MalformedURLException e) {
191: throw new IllegalArgumentException(url);
192: }
193: }
194:
195: /**
196: * This test uses a list of ids found in the test document. These ids reference
197: * elements found in a defs section. For each such element, the test will
198: * attempt to insert the target id at a given insertion point. That insertion
199: * should cause a SecurityException. If so, the test passes. Otherwise, the test
200: * will fail
201: */
202: public TestReport runImpl() throws Exception {
203: DefaultTestReport report = new DefaultTestReport(this );
204:
205: //
206: // First step:
207: //
208: // Load the input SVG into a Document object
209: //
210: String parserClassName = XMLResourceDescriptor
211: .getXMLParserClassName();
212: SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(
213: parserClassName);
214: Document doc = null;
215:
216: try {
217: doc = f.createDocument(svgURL);
218: } catch (IOException e) {
219: report.setErrorCode(ERROR_CANNOT_LOAD_SVG_DOCUMENT);
220: report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION, e
221: .getMessage());
222: report.setPassed(false);
223: return report;
224: } catch (Exception e) {
225: report.setErrorCode(ERROR_CANNOT_LOAD_SVG_DOCUMENT);
226: report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION, e
227: .getMessage());
228: report.setPassed(false);
229: return report;
230: }
231:
232: List failures = new ArrayList();
233:
234: //
235: // Do an initial processing to validate that the external
236: // stylesheet causes a SecurityException
237: //
238: MyUserAgent userAgent = buildUserAgent();
239: GVTBuilder builder = new GVTBuilder();
240: BridgeContext ctx = new BridgeContext(userAgent);
241: ctx.setDynamic(true);
242:
243: // We expect either a SecurityException or a BridgeException
244: // with ERR_URI_UNSECURE.
245: Throwable th = null;
246: try {
247: GraphicsNode gn = builder.build(ctx, doc);
248: gn.getBounds();
249: th = userAgent.getDisplayError();
250: } catch (BridgeException e) {
251: th = e;
252: } catch (SecurityException e) {
253: th = e;
254: } catch (Throwable t) {
255: th = t;
256: }
257: if (th == null) {
258: if (secure)
259: failures.add(EXTERNAL_STYLESHEET_ID);
260: } else if (th instanceof SecurityException) {
261: if (!secure)
262: failures.add(EXTERNAL_STYLESHEET_ID);
263: } else if (th instanceof BridgeException) {
264: BridgeException be = (BridgeException) th;
265: if (!secure
266: || (secure && !ERR_URI_UNSECURE
267: .equals(be.getCode()))) {
268: report
269: .setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT);
270: report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION,
271: be.getMessage());
272: report.setPassed(false);
273: return report;
274: }
275: }
276:
277: //
278: // Remove the stylesheet from the document
279: //
280: Node child = doc.getFirstChild();
281: Node next = null;
282: while (child != null) {
283: next = child.getNextSibling();
284: if (child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
285: doc.removeChild(child);
286: }
287: child = next;
288: }
289:
290: //
291: // Now, get the list of ids to be checked
292: //
293: Element root = doc.getDocumentElement();
294: String idList = root.getAttributeNS(testNS, "targetids");
295: if (idList == null || "".equals(idList)) {
296: report.setErrorCode(ERROR_NO_ID_LIST);
297: report.setPassed(false);
298: return report;
299: }
300:
301: StringTokenizer st = new StringTokenizer(idList, ",");
302: String[] ids = new String[st.countTokens()];
303: for (int i = 0; i < ids.length; i++) {
304: ids[i] = st.nextToken().trim();
305: }
306:
307: for (int i = 0; i < ids.length; i++) {
308: String id = ids[i];
309: userAgent = buildUserAgent();
310: builder = new GVTBuilder();
311: ctx = new BridgeContext(userAgent);
312: ctx.setDynamic(true);
313:
314: Document cloneDoc = (Document) doc.cloneNode(true);
315: Element insertionPoint = cloneDoc
316: .getElementById(INSERTION_POINT_ID);
317:
318: if (insertionPoint == null) {
319: report
320: .setErrorCode(ERROR_NO_INSERTION_POINT_IN_DOCUMENT);
321: report.addDescriptionEntry(
322: ENTRY_KEY_INSERTION_POINT_ID,
323: INSERTION_POINT_ID);
324: report.setPassed(false);
325: return report;
326: }
327:
328: Element target = cloneDoc.getElementById(id);
329:
330: if (target == null) {
331: report.setErrorCode(ERROR_TARGET_ID_NOT_FOUND);
332: report.addDescriptionEntry(ENTRY_KEY_TARGET_ID, id);
333: report.setPassed(false);
334: return report;
335: }
336:
337: insertionPoint.appendChild(target);
338: th = null;
339: try {
340: GraphicsNode gn = builder.build(ctx, cloneDoc);
341: gn.getBounds();
342: th = userAgent.getDisplayError();
343: } catch (BridgeException e) {
344: th = e;
345: } catch (SecurityException e) {
346: th = e;
347: } catch (Throwable t) {
348: th = t;
349: }
350: if (th == null) {
351: if (secure)
352: failures.add(id);
353: } else if (th instanceof SecurityException) {
354: if (!secure)
355: failures.add(id);
356: } else if (th instanceof BridgeException) {
357: BridgeException be = (BridgeException) th;
358: if (!secure
359: || (secure && !ERR_URI_UNSECURE.equals(be
360: .getCode()))) {
361: report
362: .setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT);
363: report.addDescriptionEntry(
364: ENTRY_KEY_ERROR_DESCRIPTION, be
365: .getMessage());
366: report.setPassed(false);
367: return report;
368: }
369: } else {
370: // Some unknown exception was displayed...
371: report
372: .setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT);
373: report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION,
374: th.getMessage());
375: report.setPassed(false);
376: return report;
377: }
378:
379: }
380:
381: if (failures.size() == 0) {
382: return reportSuccess();
383: }
384:
385: if (secure) {
386: report.setErrorCode(ERROR_UNTHROWN_SECURITY_EXCEPTIONS);
387: for (int i = 0; i < failures.size(); i++) {
388: report.addDescriptionEntry(
389: ENTRY_KEY_EXPECTED_EXCEPTION_ON, failures
390: .get(i));
391: }
392: } else {
393: report.setErrorCode(ERROR_THROWN_SECURITY_EXCEPTIONS);
394: for (int i = 0; i < failures.size(); i++) {
395: report.addDescriptionEntry(
396: ENTRY_KEY_UNEXPECTED_EXCEPTION_ON, failures
397: .get(i));
398: }
399: }
400:
401: report.setPassed(false);
402: return report;
403: }
404:
405: protected interface MyUserAgent extends UserAgent {
406: Exception getDisplayError();
407: }
408:
409: protected MyUserAgent buildUserAgent() {
410: if (secure) {
411: return new SecureUserAgent();
412: } else {
413: return new RelaxedUserAgent();
414: }
415: }
416:
417: class MyUserAgentAdapter extends UserAgentAdapter implements
418: MyUserAgent {
419: Exception ex = null;
420:
421: public void displayError(Exception ex) {
422: this .ex = ex;
423: super .displayError(ex);
424: }
425:
426: public Exception getDisplayError() {
427: return ex;
428: }
429: }
430:
431: class SecureUserAgent extends MyUserAgentAdapter {
432: public ExternalResourceSecurity getExternalResourceSecurity(
433: ParsedURL resourcePURL, ParsedURL docPURL) {
434: return new NoLoadExternalResourceSecurity();
435:
436: }
437: }
438:
439: class RelaxedUserAgent extends MyUserAgentAdapter {
440: public ExternalResourceSecurity getExternalResourceSecurity(
441: ParsedURL resourcePURL, ParsedURL docPURL) {
442: return new RelaxedExternalResourceSecurity(resourcePURL,
443: docPURL);
444:
445: }
446: }
447:
448: }
|