1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xml.internal.serializer;
  22 
  23 import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
  24 import com.sun.org.apache.xml.internal.serializer.utils.Utils;
  25 import com.sun.org.apache.xml.internal.serializer.utils.WrappedRuntimeException;
  26 import java.io.BufferedInputStream;
  27 import java.io.IOException;
  28 import java.io.InputStream;
  29 import java.security.AccessController;
  30 import java.security.PrivilegedAction;
  31 import java.util.Enumeration;
  32 import java.util.Properties;
  33 import javax.xml.transform.OutputKeys;
  34 import jdk.xml.internal.SecuritySupport;
  35 
  36 /**
  37  * This class is a factory to generate a set of default properties
  38  * of key/value pairs that are used to create a serializer through the
  39  * factory {@link SerializerFactory SerilizerFactory}.
  40  * The properties generated by this factory
  41  * may be modified to non-default values before the SerializerFactory is used to
  42  * create a Serializer.
  43  * <p>
  44  * The given output types supported are "xml", "text", and "html".
  45  * These type strings can be obtained from the
  46  * {@link Method Method} class in this package.
  47  * <p>
  48  * Other constants defined in this class are the non-standard property keys
  49  * that can be used to set non-standard property values on a java.util.Properties object
  50  * that is used to create or configure a serializer. Here are the non-standard keys:
  51  * <ul>
  52  * <li> <b>S_KEY_INDENT_AMOUNT </b> -
  53  * The non-standard property key to use to set the indentation amount.
  54  * The "indent" key needs to have a value of "yes", and this
  55  * properties value is a the number of whitespaces to indent by per
  56  * indentation level.
  57  *
  58  * <li> <b>S_KEY_CONTENT_HANDLER </b> -
  59  * This non-standard property key is used to set the name of the fully qualified
  60  * Java class that implements the ContentHandler interface.
  61  * The output of the serializer will be SAX events sent to this an
  62  * object of this class.
  63  *
  64  * <li> <b>S_KEY_ENTITIES </b> -
  65  * This non-standard property key is used to specify the name of the property file
  66  * that specifies character to entity reference mappings. A line in such a
  67  * file is has the name of the entity and the numeric (base 10) value
  68  * of the corresponding character, like this one: <br> quot=34 <br>
  69  *
  70  * <li> <b>S_USE_URL_ESCAPING </b> -
  71  * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
  72  *  use %xx escaping.
  73  *
  74  * <li> <b>S_OMIT_META_TAG </b> -
  75  * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
  76  *  otherwise be supplied.
  77  * </ul>
  78  *
  79  * @see SerializerFactory
  80  * @see Method
  81  * @see Serializer
  82  * @LastModified: Oct 2017
  83  */
  84 public final class OutputPropertiesFactory
  85 {
  86     /** S_BUILTIN_EXTENSIONS_URL is a mnemonic for the XML Namespace
  87      *(http://xml.apache.org/xalan) predefined to signify Xalan's
  88      * built-in XSLT Extensions. When used in stylesheets, this is often
  89      * bound to the "xalan:" prefix.
  90      */
  91     private static final String
  92       S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan";
  93 
  94     /**
  95      * The old built-in extension url. It is still supported for
  96      * backward compatibility.
  97      */
  98     private static final String
  99       S_BUILTIN_OLD_EXTENSIONS_URL = "http://xml.apache.org/xslt";
 100 
 101     //************************************************************
 102     //*  PUBLIC CONSTANTS
 103     //************************************************************
 104     /**
 105      * This is not a public API.
 106      * This is the built-in extensions namespace,
 107      * reexpressed in {namespaceURI} syntax
 108      * suitable for prepending to a localname to produce a "universal
 109      * name".
 110      */
 111     public static final String S_BUILTIN_EXTENSIONS_UNIVERSAL =
 112         "{" + S_BUILTIN_EXTENSIONS_URL + "}";
 113 
 114     // Some special Xalan keys.
 115 
 116     /**
 117      * The non-standard property key to use to set the
 118      * number of whitepaces to indent by, per indentation level,
 119      * if indent="yes".
 120      */
 121     public static final String S_KEY_INDENT_AMOUNT =
 122         S_BUILTIN_EXTENSIONS_UNIVERSAL + "indent-amount";
 123 
 124     /**
 125      * The non-standard property key to use to set the
 126      * number of whitepaces to indent by, per indentation level,
 127      * if indent="yes".
 128      */
 129     public static final String S_KEY_LINE_SEPARATOR =
 130         S_BUILTIN_EXTENSIONS_UNIVERSAL + "line-separator";
 131 
 132     /** This non-standard property key is used to set the name of the fully qualified
 133      * Java class that implements the ContentHandler interface.
 134      * Fully qualified name of class with a default constructor that
 135      *  implements the ContentHandler interface, where the result tree events
 136      *  will be sent to.
 137      */
 138 
 139     public static final String S_KEY_CONTENT_HANDLER =
 140         S_BUILTIN_EXTENSIONS_UNIVERSAL + "content-handler";
 141 
 142     /**
 143      * This non-standard property key is used to specify the name of the property file
 144      * that specifies character to entity reference mappings.
 145      */
 146     public static final String S_KEY_ENTITIES =
 147         S_BUILTIN_EXTENSIONS_UNIVERSAL + "entities";
 148 
 149     /**
 150      * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
 151      *  use %xx escaping. */
 152     public static final String S_USE_URL_ESCAPING =
 153         S_BUILTIN_EXTENSIONS_UNIVERSAL + "use-url-escaping";
 154 
 155     /**
 156      * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
 157      *  otherwise be supplied.
 158      */
 159     public static final String S_OMIT_META_TAG =
 160         S_BUILTIN_EXTENSIONS_UNIVERSAL + "omit-meta-tag";
 161 
 162     /**
 163      * The old built-in extension namespace, this is not a public API.
 164      */
 165     public static final String S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL =
 166         "{" + S_BUILTIN_OLD_EXTENSIONS_URL + "}";
 167 
 168     /**
 169      * This is not a public API, it is only public because it is used
 170      * by outside of this package,
 171      * it is the length of the old built-in extension namespace.
 172      */
 173     public static final int S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN =
 174         S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL.length();
 175 
 176     /**
 177      * This non-standard, Oracle-impl only property key is used as if OutputKeys.STANDALONE is specified but
 178      * without writing it out in the declaration; It can be used to reverse the change by Xalan patch 1495.
 179      * Since Xalan patch 1495 can cause incompatible behavior, this property is add for application to neutralize
 180      * the effect of Xalan patch 1495
 181      */
 182         /**
 183          * <p>Is Standalone</p>
 184          *
 185          * <ul>
 186          *   <li>
 187          *     <code>yes</code> to indicate the output is intended to be used as standalone
 188          *   </li>
 189          *   <li>
 190          *     <code>no</code> has no effect.
 191          *   </li>
 192          * </ul>
 193          */
 194     public static final String ORACLE_IS_STANDALONE = "http://www.oracle.com/xml/is-standalone";
 195 
 196     //************************************************************
 197     //*  PRIVATE CONSTANTS
 198     //************************************************************
 199 
 200     private static final String S_XSLT_PREFIX = "xslt.output.";
 201     private static final int S_XSLT_PREFIX_LEN = S_XSLT_PREFIX.length();
 202     private static final String S_XALAN_PREFIX = "org.apache.xslt.";
 203     private static final int S_XALAN_PREFIX_LEN = S_XALAN_PREFIX.length();
 204 
 205     /** Synchronization object for lazy initialization of the above tables. */
 206     private static final Object m_synch_object = new Object();
 207 
 208     /** the directory in which the various method property files are located */
 209     private static final String PROP_DIR = "com/sun/org/apache/xml/internal/serializer/";
 210     /** property file for default XML properties */
 211     private static final String PROP_FILE_XML = "output_xml.properties";
 212     /** property file for default TEXT properties */
 213     private static final String PROP_FILE_TEXT = "output_text.properties";
 214     /** property file for default HTML properties */
 215     private static final String PROP_FILE_HTML = "output_html.properties";
 216     /** property file for default UNKNOWN (Either XML or HTML, to be determined later) properties */
 217     private static final String PROP_FILE_UNKNOWN = "output_unknown.properties";
 218 
 219     //************************************************************
 220     //*  PRIVATE STATIC FIELDS
 221     //************************************************************
 222 
 223     /** The default properties of all output files. */
 224     private static Properties m_xml_properties = null;
 225 
 226     /** The default properties when method="html". */
 227     private static Properties m_html_properties = null;
 228 
 229     /** The default properties when method="text". */
 230     private static Properties m_text_properties = null;
 231 
 232     /** The properties when method="" for the "unknown" wrapper */
 233     private static Properties m_unknown_properties = null;
 234 
 235     private static final Class<?>
 236         ACCESS_CONTROLLER_CLASS = findAccessControllerClass();
 237 
 238     private static Class<?> findAccessControllerClass() {
 239         try
 240         {
 241             // This Class was introduced in JDK 1.2. With the re-architecture of
 242             // security mechanism ( starting in JDK 1.2 ), we have option of
 243             // giving privileges to certain part of code using doPrivileged block.
 244             // In JDK1.1.X applications won't be having security manager and if
 245             // there is security manager ( in applets ), code need to be signed
 246             // and trusted for having access to resources.
 247 
 248             return Class.forName("java.security.AccessController");
 249         }
 250         catch (Exception e)
 251         {
 252             //User may be using older JDK ( JDK <1.2 ). Allow him/her to use it.
 253             // But don't try to use doPrivileged
 254         }
 255 
 256         return null;
 257     }
 258 
 259     /**
 260      * Creates an empty OutputProperties with the property key/value defaults specified by
 261      * a property file.  The method argument is used to construct a string of
 262      * the form output_[method].properties (for instance, output_html.properties).
 263      * The output_xml.properties file is always used as the base.
 264      *
 265      * <p>Anything other than 'text', 'xml', and 'html', will
 266      * use the output_xml.properties file.</p>
 267      *
 268      * @param   method non-null reference to method name.
 269      *
 270      * @return Properties object that holds the defaults for the given method.
 271      */
 272     static public final Properties getDefaultMethodProperties(String method)
 273     {
 274         String fileName = null;
 275         Properties defaultProperties = null;
 276         // According to this article : Double-check locking does not work
 277         // http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-toolbox.html
 278         try
 279         {
 280             synchronized (m_synch_object)
 281             {
 282                 if (null == m_xml_properties) // double check
 283                 {
 284                     fileName = PROP_FILE_XML;
 285                     m_xml_properties = loadPropertiesFile(fileName, null);
 286                 }
 287             }
 288 
 289             if (method.equals(Method.XML))
 290             {
 291                 defaultProperties = m_xml_properties;
 292             }
 293             else if (method.equals(Method.HTML))
 294             {
 295                 if (null == m_html_properties) // double check
 296                 {
 297                     fileName = PROP_FILE_HTML;
 298                     m_html_properties =
 299                         loadPropertiesFile(fileName, m_xml_properties);
 300                 }
 301 
 302                 defaultProperties = m_html_properties;
 303             }
 304             else if (method.equals(Method.TEXT))
 305             {
 306                 if (null == m_text_properties) // double check
 307                 {
 308                     fileName = PROP_FILE_TEXT;
 309                     m_text_properties =
 310                         loadPropertiesFile(fileName, m_xml_properties);
 311                     if (null
 312                         == m_text_properties.getProperty(OutputKeys.ENCODING))
 313                     {
 314                         String mimeEncoding = Encodings.getMimeEncoding(null);
 315                         m_text_properties.put(
 316                             OutputKeys.ENCODING,
 317                             mimeEncoding);
 318                     }
 319                 }
 320 
 321                 defaultProperties = m_text_properties;
 322             }
 323             else if (method.equals(com.sun.org.apache.xml.internal.serializer.Method.UNKNOWN))
 324             {
 325                 if (null == m_unknown_properties) // double check
 326                 {
 327                     fileName = PROP_FILE_UNKNOWN;
 328                     m_unknown_properties =
 329                         loadPropertiesFile(fileName, m_xml_properties);
 330                 }
 331 
 332                 defaultProperties = m_unknown_properties;
 333             }
 334             else
 335             {
 336                 // TODO: Calculate res file from name.
 337                 defaultProperties = m_xml_properties;
 338             }
 339         }
 340         catch (IOException ioe)
 341         {
 342             throw new WrappedRuntimeException(
 343                 Utils.messages.createMessage(
 344                     MsgKey.ER_COULD_NOT_LOAD_METHOD_PROPERTY,
 345                     new Object[] { fileName, method }),
 346                 ioe);
 347         }
 348         // wrap these cached defaultProperties in a new Property object just so
 349         // that the caller of this method can't modify the default values
 350         return new Properties(defaultProperties);
 351     }
 352 
 353     /**
 354      * Load the properties file from a resource stream.  If a
 355      * key name such as "org.apache.xslt.xxx", fix up the start of
 356      * string to be a curly namespace.  If a key name starts with
 357      * "xslt.output.xxx", clip off "xslt.output.".  If a key name *or* a
 358      * key value is discovered, check for \u003a in the text, and
 359      * fix it up to be ":", since earlier versions of the JDK do not
 360      * handle the escape sequence (at least in key names).
 361      *
 362      * @param resourceName non-null reference to resource name.
 363      * @param defaults Default properties, which may be null.
 364      */
 365     static private Properties loadPropertiesFile(
 366         final String resourceName,
 367         Properties defaults)
 368         throws IOException
 369     {
 370 
 371         // This static method should eventually be moved to a thread-specific class
 372         // so that we can cache the ContextClassLoader and bottleneck all properties file
 373         // loading throughout Xalan.
 374 
 375         Properties props = new Properties(defaults);
 376 
 377         InputStream is = null;
 378         BufferedInputStream bis = null;
 379 
 380         try
 381         {
 382             if (ACCESS_CONTROLLER_CLASS != null)
 383             {
 384                 is = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
 385                         public InputStream run()
 386                         {
 387                             return OutputPropertiesFactory.class
 388                                 .getResourceAsStream(resourceName);
 389                         }
 390                     });
 391             }
 392             else
 393             {
 394                 // User may be using older JDK ( JDK < 1.2 )
 395                 is = OutputPropertiesFactory.class
 396                     .getResourceAsStream(resourceName);
 397             }
 398 
 399             bis = new BufferedInputStream(is);
 400             props.load(bis);
 401         }
 402         catch (IOException ioe)
 403         {
 404             if (defaults == null)
 405             {
 406                 throw ioe;
 407             }
 408             else
 409             {
 410                 throw new WrappedRuntimeException(
 411                     Utils.messages.createMessage(
 412                         MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
 413                         new Object[] { resourceName }),
 414                     ioe);
 415                 //"Could not load '"+resourceName+"' (check CLASSPATH), now using just the defaults ", ioe);
 416             }
 417         }
 418         catch (SecurityException se)
 419         {
 420             // Repeat IOException handling for sandbox/applet case -sc
 421             if (defaults == null)
 422             {
 423                 throw se;
 424             }
 425             else
 426             {
 427                 throw new WrappedRuntimeException(
 428                     Utils.messages.createMessage(
 429                         MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
 430                         new Object[] { resourceName }),
 431                     se);
 432                 //"Could not load '"+resourceName+"' (check CLASSPATH, applet security), now using just the defaults ", se);
 433             }
 434         }
 435         finally
 436         {
 437             if (bis != null)
 438             {
 439                 bis.close();
 440             }
 441             if (is != null)
 442             {
 443                 is.close();
 444             }
 445         }
 446 
 447         // Note that we're working at the HashTable level here,
 448         // and not at the Properties level!  This is important
 449         // because we don't want to modify the default properties.
 450         // NB: If fixupPropertyString ends up changing the property
 451         // name or value, we need to remove the old key and re-add
 452         // with the new key and value.  However, then our Enumeration
 453         // could lose its place in the HashTable.  So, we first
 454         // clone the HashTable and enumerate over that since the
 455         // clone will not change.  When we migrate to Collections,
 456         // this code should be revisited and cleaned up to use
 457         // an Iterator which may (or may not) alleviate the need for
 458         // the clone.  Many thanks to Padraig O'hIceadha
 459         // <padraig@gradient.ie> for finding this problem.  Bugzilla 2000.
 460 
 461         Enumeration<Object> keys = ((Properties) props.clone()).keys();
 462         while (keys.hasMoreElements())
 463         {
 464             String key = (String) keys.nextElement();
 465             // Now check if the given key was specified as a
 466             // System property. If so, the system property
 467             // overides the default value in the propery file.
 468             String value = null;
 469             try
 470             {
 471                 value = SecuritySupport.getSystemProperty(key);
 472             }
 473             catch (SecurityException se)
 474             {
 475                 // No-op for sandbox/applet case, leave null -sc
 476             }
 477             if (value == null)
 478                 value = (String) props.get(key);
 479 
 480             String newKey = fixupPropertyString(key, true);
 481             String newValue = null;
 482             try
 483             {
 484                 newValue = SecuritySupport.getSystemProperty(newKey);
 485             }
 486             catch (SecurityException se)
 487             {
 488                 // No-op for sandbox/applet case, leave null -sc
 489             }
 490             if (newValue == null)
 491                 newValue = fixupPropertyString(value, false);
 492             else
 493                 newValue = fixupPropertyString(newValue, false);
 494 
 495             if (key != newKey || value != newValue)
 496             {
 497                 props.remove(key);
 498                 props.put(newKey, newValue);
 499             }
 500 
 501         }
 502 
 503         return props;
 504     }
 505 
 506     /**
 507      * Fix up a string in an output properties file according to
 508      * the rules of {@link #loadPropertiesFile}.
 509      *
 510      * @param s non-null reference to string that may need to be fixed up.
 511      * @return A new string if fixup occured, otherwise the s argument.
 512      */
 513     static private String fixupPropertyString(String s, boolean doClipping)
 514     {
 515         int index;
 516         if (doClipping && s.startsWith(S_XSLT_PREFIX))
 517         {
 518             s = s.substring(S_XSLT_PREFIX_LEN);
 519         }
 520         if (s.startsWith(S_XALAN_PREFIX))
 521         {
 522             s =
 523                 S_BUILTIN_EXTENSIONS_UNIVERSAL
 524                     + s.substring(S_XALAN_PREFIX_LEN);
 525         }
 526         if ((index = s.indexOf("\\u003a")) > 0)
 527         {
 528             String temp = s.substring(index + 6);
 529             s = s.substring(0, index) + ":" + temp;
 530 
 531         }
 532         return s;
 533     }
 534 
 535 }