1 /*
2  * Copyright (c) 2016, 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.xerces.internal.impl;
22 
23 import java.io.EOFException;
24 import java.io.IOException;
25 
26 import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;
27 import com.sun.org.apache.xerces.internal.util.SymbolTable;
28 import com.sun.org.apache.xerces.internal.xni.XMLString;
29 import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager;
30 import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
31 import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
32 import com.sun.xml.internal.stream.Entity.ScannedEntity;
33 
34 /**
35  * This class scans the version of the document to determine
36  * which scanner to use: XML 1.1 or XML 1.0.
37  * The version is scanned using XML 1.1. scanner.
38  *
39  * @xerces.internal
40  *
41  * @author Neil Graham, IBM
42  * @author Elena Litani, IBM
43  */
44 public class XMLVersionDetector {
45 
46     //
47     // Constants
48     //
49 
50     private final static char[] XML11_VERSION = new char[]{'1', '.', '1'};
51 
52 
53     // property identifiers
54 
55     /** Property identifier: symbol table. */
56     protected static final String SYMBOL_TABLE =
57         Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
58 
59     /** Property identifier: error reporter. */
60     protected static final String ERROR_REPORTER =
61         Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
62 
63     /** Property identifier: entity manager. */
64     protected static final String ENTITY_MANAGER =
65         Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_MANAGER_PROPERTY;
66 
67     //
68     // Data
69     //
70 
71     /** Symbol: "version". */
72     protected final static String fVersionSymbol = "version".intern();
73 
74     // symbol:  [xml]:
75     protected static final String fXMLSymbol = "[xml]".intern();
76 
77     /** Symbol table. */
78     protected SymbolTable fSymbolTable;
79 
80     /** Error reporter. */
81     protected XMLErrorReporter fErrorReporter;
82 
83     /** Entity manager. */
84     protected XMLEntityManager fEntityManager;
85 
86     protected String fEncoding = null;
87 
88     private XMLString fVersionNum = new XMLString();
89 
90     private final char [] fExpectedVersionString = {'<', '?', 'x', 'm', 'l', ' ', 'v', 'e', 'r', 's',
91                     'i', 'o', 'n', '=', ' ', ' ', ' ', ' ', ' '};
92 
93     /**
94      *
95      *
96      * @param componentManager The component manager.
97      *
98      * @throws SAXException Throws exception if required features and
99      *                      properties cannot be found.
100      */
reset(XMLComponentManager componentManager)101     public void reset(XMLComponentManager componentManager)
102         throws XMLConfigurationException {
103 
104         // Xerces properties
105         fSymbolTable = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE);
106         fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER);
107         fEntityManager = (XMLEntityManager)componentManager.getProperty(ENTITY_MANAGER);
108         for(int i=14; i<fExpectedVersionString.length; i++ )
109             fExpectedVersionString[i] = ' ';
110     } // reset(XMLComponentManager)
111 
112     /**
113      * Reset the reference to the appropriate scanner given the version of the
114      * document and start document scanning.
115      * @param scanner - the scanner to use
116      * @param version - the version of the document (XML 1.1 or XML 1.0).
117      */
startDocumentParsing(XMLEntityHandler scanner, short version)118     public void startDocumentParsing(XMLEntityHandler scanner, short version){
119 
120         if (version == Constants.XML_VERSION_1_0){
121             fEntityManager.setScannerVersion(Constants.XML_VERSION_1_0);
122         }
123         else {
124             fEntityManager.setScannerVersion(Constants.XML_VERSION_1_1);
125         }
126         // Make sure the locator used by the error reporter is the current entity scanner.
127         fErrorReporter.setDocumentLocator(fEntityManager.getEntityScanner());
128 
129         // Note: above we reset fEntityScanner in the entity manager, thus in startEntity
130         // in each scanner fEntityScanner field must be reset to reflect the change.
131         //
132         fEntityManager.setEntityHandler(scanner);
133 
134         scanner.startEntity(fXMLSymbol, fEntityManager.getCurrentResourceIdentifier(), fEncoding, null);
135     }
136 
137 
138     /**
139      * This methods scans the XML declaration to find out the version
140      * (and provisional encoding)  of the document.
141      * The scanning is doing using XML 1.1 scanner.
142      * @param inputSource
143      * @return short - Constants.XML_VERSION_1_1 if document version 1.1,
144      *                  otherwise Constants.XML_VERSION_1_0
145      * @throws IOException
146      */
determineDocVersion(XMLInputSource inputSource)147     public short determineDocVersion(XMLInputSource inputSource) throws IOException {
148         fEncoding = fEntityManager.setupCurrentEntity(false, fXMLSymbol, inputSource, false, true);
149 
150         // Must use XML 1.0 scanner to handle whitespace correctly
151         // in the XML declaration.
152         fEntityManager.setScannerVersion(Constants.XML_VERSION_1_0);
153         XMLEntityScanner scanner = fEntityManager.getEntityScanner();
154         scanner.detectingVersion = true;
155         try {
156             if (!scanner.skipString("<?xml")) {
157                 // definitely not a well-formed 1.1 doc!
158                 scanner.detectingVersion = false;
159                 return Constants.XML_VERSION_1_0;
160             }
161             if (!scanner.skipDeclSpaces()) {
162                 fixupCurrentEntity(fEntityManager, fExpectedVersionString, 5);
163                 scanner.detectingVersion = false;
164                 return Constants.XML_VERSION_1_0;
165             }
166             if (!scanner.skipString("version")) {
167                 fixupCurrentEntity(fEntityManager, fExpectedVersionString, 6);
168                 scanner.detectingVersion = false;
169                 return Constants.XML_VERSION_1_0;
170             }
171             scanner.skipDeclSpaces();
172             // Check if the next character is '='. If it is then consume it.
173             if (scanner.peekChar() != '=') {
174                 fixupCurrentEntity(fEntityManager, fExpectedVersionString, 13);
175                 scanner.detectingVersion = false;
176                 return Constants.XML_VERSION_1_0;
177             }
178             scanner.scanChar(null);
179             scanner.skipDeclSpaces();
180             int quoteChar = scanner.scanChar(null);
181             fExpectedVersionString[14] = (char) quoteChar;
182             for (int versionPos = 0; versionPos < XML11_VERSION.length; versionPos++) {
183                 fExpectedVersionString[15 + versionPos] = (char) scanner.scanChar(null);
184             }
185             // REVISIT:  should we check whether this equals quoteChar?
186             fExpectedVersionString[18] = (char) scanner.scanChar(null);
187             fixupCurrentEntity(fEntityManager, fExpectedVersionString, 19);
188             int matched = 0;
189             for (; matched < XML11_VERSION.length; matched++) {
190                 if (fExpectedVersionString[15 + matched] != XML11_VERSION[matched])
191                     break;
192             }
193             scanner.detectingVersion = false;
194             if (matched == XML11_VERSION.length)
195                 return Constants.XML_VERSION_1_1;
196             return Constants.XML_VERSION_1_0;
197             // premature end of file
198         }
199         catch (EOFException e) {
200             fErrorReporter.reportError(
201                 XMLMessageFormatter.XML_DOMAIN,
202                 "PrematureEOF",
203                 null,
204                 XMLErrorReporter.SEVERITY_FATAL_ERROR);
205             scanner.detectingVersion = false;
206             return Constants.XML_VERSION_1_0;
207         }
208     }
209 
210     // This method prepends "length" chars from the char array,
211     // from offset 0, to the manager's fCurrentEntity.ch.
fixupCurrentEntity(XMLEntityManager manager, char [] scannedChars, int length)212     private void fixupCurrentEntity(XMLEntityManager manager,
213                 char [] scannedChars, int length) {
214         ScannedEntity currentEntity = manager.getCurrentEntity();
215         if(currentEntity.count-currentEntity.position+length > currentEntity.ch.length) {
216             //resize array; this case is hard to imagine...
217             char[] tempCh = currentEntity.ch;
218             currentEntity.ch = new char[length+currentEntity.count-currentEntity.position+1];
219             System.arraycopy(tempCh, 0, currentEntity.ch, 0, tempCh.length);
220         }
221         if(currentEntity.position < length) {
222             // have to move sensitive stuff out of the way...
223             System.arraycopy(currentEntity.ch, currentEntity.position, currentEntity.ch, length, currentEntity.count-currentEntity.position);
224             currentEntity.count += length-currentEntity.position;
225         } else {
226             // have to reintroduce some whitespace so this parses:
227             for(int i=length; i<currentEntity.position; i++)
228                 currentEntity.ch[i]=' ';
229         }
230         // prepend contents...
231         System.arraycopy(scannedChars, 0, currentEntity.ch, 0, length);
232         currentEntity.position = 0;
233         currentEntity.baseCharOffset = 0;
234         currentEntity.startPosition = 0;
235         currentEntity.columnNumber = currentEntity.lineNumber = 1;
236     }
237 
238 } // class XMLVersionDetector
239