1 /* 2 * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.util.xml; 27 28 import java.io.*; 29 import java.nio.charset.Charset; 30 import java.util.InvalidPropertiesFormatException; 31 import java.util.Map.Entry; 32 import java.util.Properties; 33 import jdk.internal.org.xml.sax.Attributes; 34 import jdk.internal.org.xml.sax.InputSource; 35 import jdk.internal.org.xml.sax.SAXException; 36 import jdk.internal.org.xml.sax.SAXParseException; 37 import jdk.internal.org.xml.sax.helpers.DefaultHandler; 38 import jdk.internal.util.xml.impl.SAXParserImpl; 39 import jdk.internal.util.xml.impl.XMLStreamWriterImpl; 40 41 /** 42 * A class used to aid in Properties load and save in XML. This class is 43 * re-implemented using a subset of SAX 44 * 45 * @author Joe Wang 46 * @since 1.8 47 */ 48 public class PropertiesDefaultHandler extends DefaultHandler { 49 50 // Elements specified in the properties.dtd 51 private static final String ELEMENT_ROOT = "properties"; 52 private static final String ELEMENT_COMMENT = "comment"; 53 private static final String ELEMENT_ENTRY = "entry"; 54 private static final String ATTR_KEY = "key"; 55 // The required DTD URI for exported properties 56 private static final String PROPS_DTD_DECL = 57 "<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">"; 58 private static final String PROPS_DTD_URI = 59 "http://java.sun.com/dtd/properties.dtd"; 60 private static final String PROPS_DTD = 61 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 62 + "<!-- DTD for properties -->" 63 + "<!ELEMENT properties ( comment?, entry* ) >" 64 + "<!ATTLIST properties" 65 + " version CDATA #FIXED \"1.0\">" 66 + "<!ELEMENT comment (#PCDATA) >" 67 + "<!ELEMENT entry (#PCDATA) >" 68 + "<!ATTLIST entry " 69 + " key CDATA #REQUIRED>"; 70 /** 71 * Version number for the format of exported properties files. 72 */ 73 private static final String EXTERNAL_XML_VERSION = "1.0"; 74 private Properties properties; 75 76 public void load(Properties props, InputStream in) 77 throws IOException, InvalidPropertiesFormatException, UnsupportedEncodingException 78 { 79 this.properties = props; 80 81 try { 82 SAXParser parser = new SAXParserImpl(); 83 parser.parse(in, this); 84 } catch (SAXException saxe) { 85 throw new InvalidPropertiesFormatException(saxe); 86 } 87 88 /** 89 * String xmlVersion = propertiesElement.getAttribute("version"); if 90 * (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) throw new 91 * InvalidPropertiesFormatException( "Exported Properties file format 92 * version " + xmlVersion + " is not supported. This java installation 93 * can read" + " versions " + EXTERNAL_XML_VERSION + " or older. You" + 94 * " may need to install a newer version of JDK."); 95 */ 96 } 97 98 public void store(Properties props, OutputStream os, String comment, Charset charset) 99 throws IOException 100 { 101 try { 102 XMLStreamWriter writer = new XMLStreamWriterImpl(os, charset); 103 writer.writeStartDocument(); 104 writer.writeDTD(PROPS_DTD_DECL); 105 writer.writeStartElement(ELEMENT_ROOT); 106 if (comment != null && comment.length() > 0) { 107 writer.writeStartElement(ELEMENT_COMMENT); 108 writer.writeCharacters(comment); 109 writer.writeEndElement(); 110 } 111 112 synchronized(props) { 113 for (Entry<Object, Object> e : props.entrySet()) { 114 final Object k = e.getKey(); 115 final Object v = e.getValue(); 116 if (k instanceof String && v instanceof String) { 117 writer.writeStartElement(ELEMENT_ENTRY); 118 writer.writeAttribute(ATTR_KEY, (String)k); 119 writer.writeCharacters((String)v); 120 writer.writeEndElement(); 121 } 122 } 123 } 124 125 writer.writeEndElement(); 126 writer.writeEndDocument(); 127 writer.flush(); 128 } catch (XMLStreamException e) { 129 if (e.getCause() instanceof UnsupportedEncodingException) { 130 throw (UnsupportedEncodingException) e.getCause(); 131 } 132 throw new IOException(e); 133 } 134 135 } 136 //////////////////////////////////////////////////////////////////// 137 // Validate while parsing 138 //////////////////////////////////////////////////////////////////// 139 static final String ALLOWED_ELEMENTS = "properties, comment, entry"; 140 static final String ALLOWED_COMMENT = "comment"; 141 //////////////////////////////////////////////////////////////////// 142 // Handler methods 143 //////////////////////////////////////////////////////////////////// 144 StringBuilder buf = new StringBuilder(); 145 boolean sawRoot = false; // whether a valid root element exists 146 boolean sawComment = false; 147 boolean validEntry = false; 148 String key; 149 String rootElm; 150 151 @Override 152 public void startElement(String uri, String localName, String qName, Attributes attributes) 153 throws SAXException 154 { 155 if (sawRoot) { 156 if (!ALLOWED_ELEMENTS.contains(qName)) { 157 fatalError(new SAXParseException("Element type \"" + qName + "\" must be declared.", null)); 158 } 159 } else { 160 // check whether the root has been declared in the DTD 161 if (rootElm == null) { 162 fatalError(new SAXParseException("An XML properties document must contain" 163 + " the DOCTYPE declaration as defined by java.util.Properties.", null)); 164 } 165 166 // check whether the element name matches the declaration 167 if (!rootElm.equals(qName)) { 168 fatalError(new SAXParseException("Document root element \"" + qName 169 + "\", must match DOCTYPE root \"" + rootElm + "\"", null)); 170 } 171 172 // this is a valid root element 173 sawRoot = true; 174 } 175 176 if (qName.equals(ELEMENT_ENTRY)) { 177 validEntry = true; 178 key = attributes.getValue(ATTR_KEY); 179 if (key == null) { 180 fatalError(new SAXParseException("Attribute \"key\" is required and " + 181 "must be specified for element type \"entry\"", null)); 182 } 183 } else if (qName.equals(ALLOWED_COMMENT)) { 184 if (sawComment) { 185 fatalError(new SAXParseException("Only one comment element may be allowed. " 186 + "The content of element type \"properties\" must match \"(comment?,entry*)\"", null)); 187 } 188 sawComment = true; 189 } 190 } 191 192 @Override 193 public void characters(char[] ch, int start, int length) throws SAXException { 194 if (validEntry) { 195 buf.append(ch, start, length); 196 } 197 } 198 199 @Override 200 public void endElement(String uri, String localName, String qName) throws SAXException { 201 if (!ALLOWED_ELEMENTS.contains(qName)) { 202 fatalError(new SAXParseException("Element: " + qName + " is invalid, must match \"(comment?,entry*)\".", null)); 203 } 204 205 if (validEntry) { 206 properties.setProperty(key, buf.toString()); 207 buf.delete(0, buf.length()); 208 validEntry = false; 209 } 210 } 211 212 @Override 213 public void notationDecl(String name, String publicId, String systemId) throws SAXException { 214 rootElm = name; 215 } 216 217 @Override 218 public InputSource resolveEntity(String pubid, String sysid) 219 throws SAXException, IOException { 220 { 221 if (sysid.equals(PROPS_DTD_URI)) { 222 // The properties DTD is known to the handler, no need to parse it 223 return null; 224 } 225 throw new SAXException("Invalid system identifier: " + sysid); 226 } 227 } 228 229 @Override 230 public void error(SAXParseException x) throws SAXException { 231 throw x; 232 } 233 234 @Override 235 public void fatalError(SAXParseException x) throws SAXException { 236 throw x; 237 } 238 239 @Override 240 public void warning(SAXParseException x) throws SAXException { 241 throw x; 242 } 243 244 // SAX2 extension from DTDHandler 245 246 @Override 247 public void startDTD (String name, String publicId, String systemId) throws SAXException 248 { 249 if (!ELEMENT_ROOT.equals(name) || !PROPS_DTD_URI.equals(systemId)) { 250 fatalError(new SAXParseException("An XML properties document must contain" 251 + " the DOCTYPE declaration as defined by java.util.Properties.", null)); 252 } 253 rootElm = name; 254 } 255 256 @Override 257 public void endDTD () throws SAXException 258 { 259 // no op 260 } 261 262 @Override 263 public void startInternalSub () throws SAXException 264 { 265 fatalError(new SAXParseException("Internal DTD subset is not allowed. " + 266 "The Properties XML document must have the following DOCTYPE declaration: \n" + 267 PROPS_DTD_DECL, null)); 268 } 269 }