| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.bridge; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.StringTokenizer; |
| import java.util.List; |
| import java.util.ArrayList; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| import org.apache.batik.anim.dom.SAXSVGDocumentFactory; |
| import org.apache.batik.gvt.GraphicsNode; |
| import org.apache.batik.test.AbstractTest; |
| import org.apache.batik.test.DefaultTestReport; |
| import org.apache.batik.test.TestReport; |
| import org.apache.batik.util.ParsedURL; |
| import org.apache.batik.util.XMLResourceDescriptor; |
| |
| /** |
| * This test validates that SecurityExceptions are generated when |
| * the user is trying the access external resources and the UserAgent |
| * disallows that. |
| * |
| * In the following, 'unsecure' means an external resource coming from |
| * a different location than the file referencing it. |
| * |
| * This test works with an SVG file containing an unsecure stylesheet |
| * and a set of unsecure elements of all kinds, such as <image> |
| * <use> or <feImage>. All these elements are defined |
| * in a defs section. The test tries to load the document and validates |
| * that a SecurityException is thrown (because of the unsecure |
| * stylesheet). Then, the test iterates over the various unsecure |
| * elements, inserting them into the document outside the defs |
| * section, which should result in a SecurityException in each case. |
| * |
| * There is a property (secure) to have the test work the opposite |
| * way and check that no SecurityException is thrown if access |
| * to external resources is allowed. |
| * |
| * @author <a href="mailto:vhardy@apache.org">Vincent Hardy</a> |
| * @version $Id$ |
| */ |
| |
| public class ExternalResourcesTest extends AbstractTest |
| implements ErrorConstants { |
| /** |
| * Error when the input file cannot be loaded into a |
| * Document object |
| * {0} = IOException message |
| */ |
| public static final String ERROR_CANNOT_LOAD_SVG_DOCUMENT |
| = "ExternalResourcesTest.error.cannot.load.svg.document"; |
| |
| /** |
| * Error while processing the document |
| * {0} = BridgeException message |
| */ |
| public static final String ERROR_WHILE_PROCESSING_SVG_DOCUMENT |
| = "ExternalResourcesTest.error.while.processing.svg.document"; |
| |
| /** |
| * Error: an expected exception was not thrown |
| * {0} = List of ids for which the exception was not thrown |
| */ |
| public static final String ERROR_UNTHROWN_SECURITY_EXCEPTIONS |
| = "ExternalResourcesTest.error.unthrown.security.exceptions"; |
| |
| /** |
| * Error: an unexpected exception was thrown |
| * {0} = List of ids for which an exception was thrown |
| */ |
| public static final String ERROR_THROWN_SECURITY_EXCEPTIONS |
| = "ExternalResourcesTest.error.thrown.security.exceptions"; |
| |
| /** |
| * Error when the insertion point cannot be found in the |
| * test document |
| * {0} = insertion point id |
| */ |
| public static final String ERROR_NO_INSERTION_POINT_IN_DOCUMENT |
| = "ExternalResourceTest.error.no.insertion.point.in.document"; |
| |
| /** |
| * Error when the test could not find a list of ids for testing |
| */ |
| public static final String ERROR_NO_ID_LIST |
| = "ExternalResourceTest.error.no.id.list"; |
| |
| /** |
| * Error when one of the target id cannot be found |
| * {0} = id which was not found |
| */ |
| public static final String ERROR_TARGET_ID_NOT_FOUND |
| = "ExternalResourcesTest.error.target.id.not.found"; |
| |
| /** |
| * Entry describing the error |
| */ |
| public static final String ENTRY_KEY_ERROR_DESCRIPTION |
| = "ExternalResourcesTest.entry.key.error.description"; |
| |
| public static final String ENTRY_KEY_INSERTION_POINT_ID |
| = "ExternalResourcesTest.entry.key.insertion.point.id"; |
| |
| public static final String ENTRY_KEY_TARGET_ID |
| = "ExternalResourcesTest.entry.target.id"; |
| |
| public static final String ENTRY_KEY_EXPECTED_EXCEPTION_ON |
| = "ExternalResourcesTest.entry.key.expected.exception.on"; |
| |
| public static final String ENTRY_KEY_UNEXPECTED_EXCEPTION_ON |
| = "ExternalResourcesTest.entry.key.unexpected.exception.on"; |
| |
| /** |
| * Pseudo id for the external stylesheet test |
| */ |
| public static final String EXTERNAL_STYLESHEET_ID |
| = "external-stylesheet"; |
| |
| /** |
| * Test Namespace |
| */ |
| public static final String testNS = "http://xml.apache.org/batik/test"; |
| |
| /** |
| * Id of the element where unsecure content is inserted |
| */ |
| public static final String INSERTION_POINT_ID = "insertionPoint"; |
| |
| /** |
| * Location of test files in filesystem. |
| */ |
| public static final String FILE_DIR = |
| "test-resources/org/apache/batik/bridge/"; |
| /** |
| * Controls whether the test works in secure mode or not |
| */ |
| protected boolean secure = true; |
| |
| String svgURL; |
| |
| public void setId(String id){ |
| super.setId(id); |
| String file = id; |
| int idx = file.indexOf('.'); |
| if (idx != -1) { |
| file = file.substring(0,idx); |
| } |
| svgURL = resolveURL(FILE_DIR + file + ".svg"); |
| } |
| |
| public Boolean getSecure(){ |
| return secure ? Boolean.TRUE : Boolean.FALSE; |
| } |
| |
| public void setSecure(Boolean secure) { |
| this.secure = secure; |
| } |
| |
| /** |
| * Resolves the input string as follows. |
| * + First, the string is interpreted as a file description. |
| * If the file exists, then the file name is turned into |
| * a URL. |
| * + Otherwise, the string is supposed to be a URL. If it |
| * is an invalid URL, an IllegalArgumentException is thrown. |
| */ |
| protected String resolveURL(String url){ |
| // Is url a file? |
| File f = (new File(url)).getAbsoluteFile(); |
| if(f.getParentFile().exists()){ |
| try{ |
| return f.toURI().toURL().toString(); |
| }catch(MalformedURLException e){ |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| // url is not a file. It must be a regular URL... |
| try{ |
| return (new URL(url)).toString(); |
| }catch(MalformedURLException e){ |
| throw new IllegalArgumentException(url); |
| } |
| } |
| |
| /** |
| * This test uses a list of ids found in the test document. These ids reference |
| * elements found in a defs section. For each such element, the test will |
| * attempt to insert the target id at a given insertion point. That insertion |
| * should cause a SecurityException. If so, the test passes. Otherwise, the test |
| * will fail |
| */ |
| public TestReport runImpl() throws Exception{ |
| DefaultTestReport report |
| = new DefaultTestReport(this); |
| |
| // |
| // First step: |
| // |
| // Load the input SVG into a Document object |
| // |
| String parserClassName = XMLResourceDescriptor.getXMLParserClassName(); |
| SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parserClassName); |
| Document doc = null; |
| |
| try { |
| doc = f.createDocument(svgURL); |
| } catch(IOException e){ |
| report.setErrorCode(ERROR_CANNOT_LOAD_SVG_DOCUMENT); |
| report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION, |
| e.getMessage()); |
| report.setPassed(false); |
| return report; |
| } catch(Exception e){ |
| report.setErrorCode(ERROR_CANNOT_LOAD_SVG_DOCUMENT); |
| report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION, |
| e.getMessage()); |
| report.setPassed(false); |
| return report; |
| } |
| |
| List failures = new ArrayList(); |
| |
| // |
| // Do an initial processing to validate that the external |
| // stylesheet causes a SecurityException |
| // |
| MyUserAgent userAgent = buildUserAgent(); |
| GVTBuilder builder = new GVTBuilder(); |
| BridgeContext ctx = new BridgeContext(userAgent); |
| ctx.setDynamic(true); |
| |
| // We expect either a SecurityException or a BridgeException |
| // with ERR_URI_UNSECURE. |
| Throwable th = null; |
| try { |
| GraphicsNode gn = builder.build(ctx, doc); |
| gn.getBounds(); |
| th = userAgent.getDisplayError(); |
| } catch (BridgeException e){ |
| th = e; |
| } catch (SecurityException e) { |
| th = e; |
| } catch (Throwable t) { |
| th = t; |
| } |
| if (th == null) { |
| if (secure) |
| failures.add(EXTERNAL_STYLESHEET_ID); |
| } else if (th instanceof SecurityException) { |
| if (!secure) |
| failures.add(EXTERNAL_STYLESHEET_ID); |
| } else if (th instanceof BridgeException) { |
| BridgeException be = (BridgeException)th; |
| if (!secure || |
| (secure && !ERR_URI_UNSECURE.equals(be.getCode()))) { |
| report.setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT); |
| report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION, |
| be.getMessage()); |
| report.setPassed(false); |
| return report; |
| } |
| } |
| |
| // |
| // Remove the stylesheet from the document |
| // |
| Node child = doc.getFirstChild(); |
| Node next = null; |
| while (child != null) { |
| next = child.getNextSibling(); |
| if (child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { |
| doc.removeChild(child); |
| } |
| child = next; |
| } |
| |
| // |
| // Now, get the list of ids to be checked |
| // |
| Element root = doc.getDocumentElement(); |
| String idList = root.getAttributeNS(testNS, "targetids"); |
| if (idList == null || "".equals(idList)) { |
| report.setErrorCode(ERROR_NO_ID_LIST); |
| report.setPassed(false); |
| return report; |
| } |
| |
| StringTokenizer st = new StringTokenizer(idList, ","); |
| String[] ids = new String[st.countTokens()]; |
| for (int i=0; i<ids.length; i++) { |
| ids[i] = st.nextToken().trim(); |
| } |
| |
| for (String id : ids) { |
| userAgent = buildUserAgent(); |
| builder = new GVTBuilder(); |
| ctx = new BridgeContext(userAgent); |
| ctx.setDynamic(true); |
| |
| Document cloneDoc = (Document) doc.cloneNode(true); |
| Element insertionPoint = cloneDoc.getElementById(INSERTION_POINT_ID); |
| |
| if (insertionPoint == null) { |
| report.setErrorCode(ERROR_NO_INSERTION_POINT_IN_DOCUMENT); |
| report.addDescriptionEntry(ENTRY_KEY_INSERTION_POINT_ID, |
| INSERTION_POINT_ID); |
| report.setPassed(false); |
| return report; |
| } |
| |
| Element target = cloneDoc.getElementById(id); |
| |
| if (target == null) { |
| report.setErrorCode(ERROR_TARGET_ID_NOT_FOUND); |
| report.addDescriptionEntry(ENTRY_KEY_TARGET_ID, |
| id); |
| report.setPassed(false); |
| return report; |
| } |
| |
| insertionPoint.appendChild(target); |
| th = null; |
| try { |
| GraphicsNode gn = builder.build(ctx, cloneDoc); |
| gn.getBounds(); |
| th = userAgent.getDisplayError(); |
| } catch (BridgeException e) { |
| th = e; |
| } catch (SecurityException e) { |
| th = e; |
| } catch (Throwable t) { |
| th = t; |
| } |
| if (th == null) { |
| if (secure) |
| failures.add(id); |
| } else if (th instanceof SecurityException) { |
| if (!secure) |
| failures.add(id); |
| } else if (th instanceof BridgeException) { |
| BridgeException be = (BridgeException) th; |
| if (!secure || |
| (secure && !ERR_URI_UNSECURE.equals(be.getCode()))) { |
| report.setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT); |
| report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION, |
| be.getMessage()); |
| report.setPassed(false); |
| return report; |
| } |
| } else { |
| // Some unknown exception was displayed... |
| report.setErrorCode(ERROR_WHILE_PROCESSING_SVG_DOCUMENT); |
| report.addDescriptionEntry(ENTRY_KEY_ERROR_DESCRIPTION, |
| th.getMessage()); |
| report.setPassed(false); |
| return report; |
| } |
| |
| } |
| |
| if (failures.size() == 0) { |
| return reportSuccess(); |
| } |
| |
| if (secure) { |
| report.setErrorCode(ERROR_UNTHROWN_SECURITY_EXCEPTIONS); |
| for (Object failure : failures) { |
| report.addDescriptionEntry(ENTRY_KEY_EXPECTED_EXCEPTION_ON, |
| failure); |
| } |
| } else { |
| report.setErrorCode(ERROR_THROWN_SECURITY_EXCEPTIONS); |
| for (Object failure : failures) { |
| report.addDescriptionEntry(ENTRY_KEY_UNEXPECTED_EXCEPTION_ON, |
| failure); |
| } |
| } |
| |
| report.setPassed(false); |
| return report; |
| } |
| |
| protected interface MyUserAgent extends UserAgent { |
| Exception getDisplayError(); |
| } |
| |
| protected MyUserAgent buildUserAgent(){ |
| if (secure) { |
| return new SecureUserAgent(); |
| } else { |
| return new RelaxedUserAgent(); |
| } |
| } |
| |
| class MyUserAgentAdapter extends UserAgentAdapter implements MyUserAgent { |
| Exception ex = null; |
| public void displayError(Exception ex) { |
| this.ex = ex; |
| super.displayError(ex); |
| } |
| |
| public Exception getDisplayError() { return ex; } |
| } |
| |
| class SecureUserAgent extends MyUserAgentAdapter { |
| public ExternalResourceSecurity |
| getExternalResourceSecurity(ParsedURL resourcePURL, |
| ParsedURL docPURL){ |
| return new NoLoadExternalResourceSecurity(); |
| |
| } |
| } |
| |
| class RelaxedUserAgent extends MyUserAgentAdapter { |
| public ExternalResourceSecurity |
| getExternalResourceSecurity(ParsedURL resourcePURL, |
| ParsedURL docPURL){ |
| return new RelaxedExternalResourceSecurity(resourcePURL, |
| docPURL); |
| |
| } |
| } |
| |
| } |