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.test.svg;
020:
021: import java.io.File;
022: import java.io.FilePermission;
023: import java.net.MalformedURLException;
024: import java.net.URL;
025: import java.security.AccessControlContext;
026: import java.security.AccessController;
027: import java.security.CodeSource;
028: import java.security.Permission;
029: import java.security.PermissionCollection;
030: import java.security.Permissions;
031: import java.security.Policy;
032: import java.security.PrivilegedActionException;
033: import java.security.PrivilegedExceptionAction;
034: import java.security.ProtectionDomain;
035: import java.security.cert.Certificate;
036: import java.util.Enumeration;
037:
038: import org.apache.batik.bridge.BaseScriptingEnvironment;
039: import org.apache.batik.bridge.BridgeContext;
040: import org.apache.batik.bridge.BridgeException;
041: import org.apache.batik.bridge.DefaultExternalResourceSecurity;
042: import org.apache.batik.bridge.DefaultScriptSecurity;
043: import org.apache.batik.bridge.EmbededExternalResourceSecurity;
044: import org.apache.batik.bridge.EmbededScriptSecurity;
045: import org.apache.batik.bridge.ExternalResourceSecurity;
046: import org.apache.batik.bridge.GVTBuilder;
047: import org.apache.batik.bridge.NoLoadExternalResourceSecurity;
048: import org.apache.batik.bridge.NoLoadScriptSecurity;
049: import org.apache.batik.bridge.RelaxedExternalResourceSecurity;
050: import org.apache.batik.bridge.RelaxedScriptSecurity;
051: import org.apache.batik.bridge.ScriptSecurity;
052: import org.apache.batik.bridge.UserAgentAdapter;
053: import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
054: import org.apache.batik.test.AbstractTest;
055: import org.apache.batik.test.TestReport;
056: import org.apache.batik.util.ApplicationSecurityEnforcer;
057: import org.apache.batik.util.ParsedURL;
058: import org.apache.batik.util.XMLResourceDescriptor;
059:
060: import org.w3c.dom.Document;
061: import org.w3c.dom.Element;
062:
063: /**
064: * This test takes an SVG file as an input. It processes the input SVG
065: * (meaning it turns it into a GVT tree) and then dispatches the 'onload'
066: * event.
067: *
068: * In that process, the test checks for the occurence of a specific
069: * exception type and, for BridgeExceptions, for a given error code.
070: *
071: * If an exception of the given type (and, optionally, code) happens,
072: * then the test passes. If an exception of an unexpected type
073: * (or code, for BridgeExceptions) happens, or if no exception happens,
074: * the test fails.
075: *
076: * The following properties control the test's operation:
077: * - Scripts: list of allowed script types (e.g., "application/java-archive")
078: * - ScriptOrigin: "ANY", "DOCUMENT", "EMBEDED", "NONE"
079: * - ResourceOrigin: "ANY", "DOCUMENT", "EMBEDED", "NONE"
080: * - ExpectedExceptionClass (e.g., "java.lang.SecurityException")
081: * - ExpectedErrorCode (e.g., "err.uri.unsecure")
082: * - Validate (e.g., "true")
083: *
084: * @author <a href="mailto:vhardy@apache.org">Vincent Hardy</a>
085: * @version $Id: SVGOnLoadExceptionTest.java 475668 2006-11-16 09:57:47Z dvholten $
086: */
087: public class SVGOnLoadExceptionTest extends AbstractTest {
088: /**
089: * Value for the script having successfully run.
090: */
091: public static final String RAN = "ran";
092:
093: /**
094: * Error when the expected exception did not occur
095: */
096: public static final String ERROR_EXCEPTION_DID_NOT_OCCUR = "SVGOnLoadExceptionTest.error.exception.did.not.occur";
097:
098: /**
099: * Error when an exception occured, but not of the expected
100: * class
101: */
102: public static final String ERROR_UNEXPECTED_EXCEPTION = "SVGOnLoadExceptionTest.error.unexpected.exception";
103:
104: /**
105: * Error when a BridgeException occured, as expected, but
106: * with an unexpected error code
107: */
108: public static final String ERROR_UNEXPECTED_ERROR_CODE = "SVGOnLoadExceptionTest.error.unexpected.error.code";
109:
110: /**
111: * Error when the script does not run as expected.
112: */
113: public static final String ERROR_SCRIPT_DID_NOT_RUN = "SVGOnLoadExceptionTest.error.script.did.not.run";
114:
115: /**
116: * Entry describing the unexpected exception
117: */
118: public static final String ENTRY_KEY_UNEXPECTED_EXCEPTION = "SVGOnLoadExceptionTest.entry.key.unexpected.exception";
119:
120: /**
121: * Entry describing the unexpected error code
122: */
123: public static final String ENTRY_KEY_UNEXPECTED_ERROR_CODE = "SVGOnLoadExceptionTest.entry.key.unexpected.error.code";
124:
125: /**
126: * Entry describign the expected error code
127: */
128: public static final String ENTRY_KEY_EXPECTED_ERROR_CODE = "SVGOnLoadExceptionTest.entry.key.expected.error.code";
129:
130: /**
131: * Entry describing the expected exception
132: */
133: public static final String ENTRY_KEY_EXPECTED_EXCEPTION = "SVGOnLoadExceptionTest.entry.key.expected.exception";
134:
135: /**
136: * Entry describing the unexpected exception
137: */
138: public static final String ENTRY_KEY_UNEXPECTED_RESULT = "SVGOnLoadExceptionTest.entry.key.unexpected.result";
139:
140: /**
141: * Value used to disable error code check on BridgeExceptions
142: */
143: public static final String ERROR_CODE_NO_CHECK = "noCheck";
144:
145: /**
146: * Test Namespace
147: */
148: public static final String testNS = "http://xml.apache.org/batik/test";
149:
150: /**
151: * The URL for the input SVG document to be tested
152: */
153: protected String svgURL;
154:
155: /**
156: * The allowed script types
157: */
158: protected String scripts = "text/ecmascript, application/java-archive";
159:
160: /**
161: * Name of the expected exception class
162: */
163: protected String expectedExceptionClass = "org.apache.batik.bridge.Exception";
164:
165: /**
166: * Expected error code (for BridgeExceptions)
167: */
168: protected String expectedErrorCode = "none";
169:
170: /**
171: * The allowed script origin
172: */
173: protected String scriptOrigin = "ANY";
174:
175: /**
176: * The allowed external resource origin
177: */
178: protected String resourceOrigin = "ANY";
179:
180: /**
181: * True if the scripts are run securely (i.e., with a security manager)
182: */
183: protected boolean secure = false;
184:
185: /**
186: * Controls whether or not the input SVG document should be validated
187: */
188: protected Boolean validate = Boolean.FALSE;
189:
190: /**
191: * The name of the test file
192: */
193: protected String fileName;
194:
195: /**
196: * Controls whether on not the document should be processed from
197: * a 'restricted' context, one with no createClassLoader permission.
198: */
199: protected boolean restricted = false;
200:
201: public boolean getRestricted() {
202: return restricted;
203: }
204:
205: public void setRestricted(boolean restricted) {
206: this .restricted = restricted;
207: }
208:
209: public void setScripts(String scripts) {
210: this .scripts = scripts;
211: }
212:
213: public String getScripts() {
214: return scripts;
215: }
216:
217: public void setScriptOrigin(String scriptOrigin) {
218: this .scriptOrigin = scriptOrigin;
219: }
220:
221: public String getScriptOrigin() {
222: return this .scriptOrigin;
223: }
224:
225: public void setResourceOrigin(String resourceOrigin) {
226: this .resourceOrigin = resourceOrigin;
227: }
228:
229: public String getResourceOrigin() {
230: return this .resourceOrigin;
231: }
232:
233: public void setSecure(boolean secure) {
234: this .secure = secure;
235: }
236:
237: public boolean getSecure() {
238: return secure;
239: }
240:
241: public void setExpectedExceptionClass(String expectedExceptionClass) {
242: this .expectedExceptionClass = expectedExceptionClass;
243: }
244:
245: public String getExpectedExceptionClass() {
246: return this .expectedExceptionClass;
247: }
248:
249: public void setExpectedErrorCode(String expectedErrorCode) {
250: this .expectedErrorCode = expectedErrorCode;
251: }
252:
253: public String getExpectedErrorCode() {
254: return this .expectedErrorCode;
255: }
256:
257: public Boolean getValidate() {
258: return validate;
259: }
260:
261: public void setValidate(Boolean validate) {
262: this .validate = validate;
263: if (this .validate == null) {
264: this .validate = Boolean.FALSE;
265: }
266: }
267:
268: /**
269: * Default constructor
270: */
271: public SVGOnLoadExceptionTest() {
272: }
273:
274: public void setId(String id) {
275: super .setId(id);
276:
277: if (id != null) {
278: int i = id.indexOf("(");
279: if (i != -1) {
280: id = id.substring(0, i);
281: }
282: fileName = "test-resources/org/apache/batik/" + id + ".svg";
283: svgURL = resolveURL(fileName);
284: }
285: }
286:
287: /**
288: * Resolves the input string as follows.
289: * + First, the string is interpreted as a file description.
290: * If the file exists, then the file name is turned into
291: * a URL.
292: * + Otherwise, the string is supposed to be a URL. If it
293: * is an invalid URL, an IllegalArgumentException is thrown.
294: */
295: protected String resolveURL(String url) {
296: // Is url a file?
297: File f = (new File(url)).getAbsoluteFile();
298: if (f.getParentFile().exists()) {
299: try {
300: return f.toURL().toString();
301: } catch (MalformedURLException e) {
302: throw new IllegalArgumentException();
303: }
304: }
305:
306: // url is not a file. It must be a regular URL...
307: try {
308: return (new URL(url)).toString();
309: } catch (MalformedURLException e) {
310: throw new IllegalArgumentException(url);
311: }
312: }
313:
314: /**
315: * Run this test and produce a report.
316: * The test goes through the following steps: <ul>
317: * <li>load the input SVG into a Document</li>
318: * <li>build the GVT tree corresponding to the
319: * Document and dispatch the 'onload' event</li>
320: * </ul>
321: *
322: */
323: public TestReport runImpl() throws Exception {
324: ApplicationSecurityEnforcer ase = new ApplicationSecurityEnforcer(
325: this .getClass(),
326: "org/apache/batik/apps/svgbrowser/resources/svgbrowser.policy");
327:
328: if (secure) {
329: ase.enforceSecurity(true);
330: }
331:
332: try {
333: if (!restricted) {
334: return testImpl();
335: } else {
336: // Emulate calling from restricted code. We create a
337: // calling context with only the permission to read
338: // the file.
339: Policy policy = Policy.getPolicy();
340: URL classesURL = (new File("classes")).toURL();
341: CodeSource cs = new CodeSource(classesURL,
342: (Certificate[]) null);
343: PermissionCollection permissionsOrig = policy
344: .getPermissions(cs);
345: Permissions permissions = new Permissions();
346: Enumeration iter = permissionsOrig.elements();
347: while (iter.hasMoreElements()) {
348: Permission p = (Permission) iter.nextElement();
349: if (!(p instanceof RuntimePermission)) {
350: if (!(p instanceof java.security.AllPermission)) {
351: permissions.add(p);
352: }
353: } else {
354: if (!"createClassLoader".equals(p.getName())) {
355: permissions.add(p);
356: }
357: }
358: }
359:
360: permissions.add(new FilePermission(fileName, "read"));
361: permissions.add(new RuntimePermission(
362: "accessDeclaredMembers"));
363:
364: ProtectionDomain domain;
365: AccessControlContext ctx;
366: domain = new ProtectionDomain(null, permissions);
367: ctx = new AccessControlContext(
368: new ProtectionDomain[] { domain });
369:
370: try {
371: return (TestReport) AccessController.doPrivileged(
372: new PrivilegedExceptionAction() {
373: public Object run() throws Exception {
374: return testImpl();
375: }
376: }, ctx);
377: } catch (PrivilegedActionException pae) {
378: throw pae.getException();
379: }
380: }
381: } finally {
382: ase.enforceSecurity(false);
383: }
384: }
385:
386: /**
387: * Implementation helper
388: */
389: protected TestReport testImpl() {
390: //
391: // First step:
392: //
393: // Load the input SVG into a Document object
394: //
395: String parserClassName = XMLResourceDescriptor
396: .getXMLParserClassName();
397: SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(
398: parserClassName);
399: f.setValidating(validate.booleanValue());
400: Document doc = null;
401:
402: try {
403: doc = f.createDocument(svgURL);
404: } catch (Exception e) {
405: e.printStackTrace();
406: return handleException(e);
407: }
408:
409: //
410: // Second step:
411: //
412: // Now that the SVG file has been loaded, build
413: // a GVT Tree from it
414: //
415: TestUserAgent userAgent = buildUserAgent();
416: GVTBuilder builder = new GVTBuilder();
417: BridgeContext ctx = new BridgeContext(userAgent);
418: ctx.setDynamic(true);
419: Exception e = null;
420: try {
421: builder.build(ctx, doc);
422: BaseScriptingEnvironment scriptEnvironment = new BaseScriptingEnvironment(
423: ctx);
424: scriptEnvironment.loadScripts();
425: scriptEnvironment.dispatchSVGLoadEvent();
426: } catch (Exception ex) {
427: e = ex;
428: } finally {
429: if (e == null && userAgent.e != null) {
430: e = userAgent.e;
431: }
432:
433: if (e != null) {
434: return handleException(e);
435: }
436: }
437:
438: //
439: // If we got here, it means that an exception did not
440: // happen. Check if this is expected.
441: TestReport report = null;
442: if (expectedExceptionClass == null) {
443: // No error was expected then check that the script ran.
444: Element elem = doc.getElementById("testResult");
445: String s = elem.getAttributeNS(null, "result");
446: if (RAN.equals(s)) {
447: report = reportSuccess();
448: } else {
449: report = reportError(ERROR_SCRIPT_DID_NOT_RUN);
450: report.addDescriptionEntry(ENTRY_KEY_UNEXPECTED_RESULT,
451: s);
452: }
453: }
454: if (report == null) {
455: report = reportError(ERROR_EXCEPTION_DID_NOT_OCCUR);
456: report.addDescriptionEntry(ENTRY_KEY_EXPECTED_EXCEPTION,
457: expectedExceptionClass);
458: }
459: return report;
460: }
461:
462: /**
463: * Compares the input exception with the expected exception
464: * If they match, then the test passes. Otherwise, the test fails
465: */
466: protected TestReport handleException(Exception e) {
467: if (!isMatch(e.getClass(), expectedExceptionClass)) {
468: TestReport report = reportError(ERROR_UNEXPECTED_EXCEPTION);
469: report.addDescriptionEntry(ENTRY_KEY_UNEXPECTED_EXCEPTION,
470: e.getClass().getName());
471: report.addDescriptionEntry(ENTRY_KEY_EXPECTED_EXCEPTION,
472: expectedExceptionClass);
473: return report;
474: } else {
475: if (!ERROR_CODE_NO_CHECK.equals(expectedErrorCode)
476: && e instanceof BridgeException) {
477: if (!expectedErrorCode.equals(((BridgeException) e)
478: .getCode())) {
479: TestReport report = reportError(ERROR_UNEXPECTED_ERROR_CODE);
480: report.addDescriptionEntry(
481: ENTRY_KEY_UNEXPECTED_ERROR_CODE,
482: ((BridgeException) e).getCode());
483: report.addDescriptionEntry(
484: ENTRY_KEY_EXPECTED_ERROR_CODE,
485: expectedErrorCode);
486: return report;
487: }
488: }
489: return reportSuccess();
490: }
491: }
492:
493: /**
494: * Check if the input class' name (or one of its base classes) matches
495: * the input name.
496: */
497: protected boolean isMatch(final Class cl, final String name) {
498: if (cl == null) {
499: return false;
500: } else if (cl.getName().equals(name)) {
501: return true;
502: } else {
503: return isMatch(cl.getSuperclass(), name);
504: }
505: }
506:
507: /**
508: * Give subclasses a chance to build their own UserAgent
509: */
510: protected TestUserAgent buildUserAgent() {
511: return new TestUserAgent();
512: }
513:
514: class TestUserAgent extends UserAgentAdapter {
515: Exception e;
516:
517: public ExternalResourceSecurity getExternalResourceSecurity(
518: ParsedURL resourceURL, ParsedURL docURL) {
519: if ("ANY".equals(resourceOrigin)) {
520: return new RelaxedExternalResourceSecurity(resourceURL,
521: docURL);
522: } else if ("DOCUMENT".equals(resourceOrigin)) {
523: return new DefaultExternalResourceSecurity(resourceURL,
524: docURL);
525: } else if ("EMBEDED".equals(resourceOrigin)) {
526: return new EmbededExternalResourceSecurity(resourceURL);
527: } else {
528: return new NoLoadExternalResourceSecurity();
529: }
530: }
531:
532: public ScriptSecurity getScriptSecurity(String scriptType,
533: ParsedURL scriptURL, ParsedURL docURL) {
534: ScriptSecurity result = null;
535: if (scripts.indexOf(scriptType) == -1) {
536: result = new NoLoadScriptSecurity(scriptType);
537: } else {
538: if ("ANY".equals(scriptOrigin)) {
539: result = new RelaxedScriptSecurity(scriptType,
540: scriptURL, docURL);
541: } else if ("DOCUMENT".equals(scriptOrigin)) {
542: result = new DefaultScriptSecurity(scriptType,
543: scriptURL, docURL);
544: } else if ("EMBEDED".equals(scriptOrigin)) {
545: result = new EmbededScriptSecurity(scriptType,
546: scriptURL, docURL);
547: } else {
548: result = new NoLoadScriptSecurity(scriptType);
549: }
550: }
551: return result;
552: }
553:
554: public void displayError(Exception e) {
555: this.e = e;
556: }
557: }
558:
559: }
|