Commit 31cbdec4 authored by Emmanuel Bourg's avatar Emmanuel Bourg

New upstream version 8.5.10

parent cfa27289
......@@ -108,6 +108,18 @@ package.
The location where the source has been placed will be further referred as
${tomcat.source}.
The Tomcat local build process does not modify line-endings. The svn repository
is configured so that all files will be checked out with the line-ending
appropriate for the current platform. When using a source package you should
ensure that you use the source package that has the appropriate line-ending
for your platform:
zip -> CRLF
tar.gz -> LF
Note that the release build process does modify line-endings to ensure that
each release package has the appropriate line-endings.
(3.2) Building
1. The build is controlled by creating a ${tomcat.source}/build.properties
......
Apache Tomcat
Copyright 1999-2016 The Apache Software Foundation
Copyright 1999-2017 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
......
......@@ -25,7 +25,7 @@
# ----- Version Control Flags -----
version.major=8
version.minor=5
version.build=9
version.build=10
version.patch=0
version.suffix=
......@@ -59,6 +59,10 @@ execute.findbugs=false
# multicast tests to work
java.net.preferIPv4Stack=false
# Specify the default (true) else the empty string will be used which
# effectively changes the setting to false
org.apache.tomcat.util.net.NioSelectorShared=true
# Location of GPG executable (used only for releases)
gpg.exec=/path/to/gpg
......
......@@ -546,6 +546,7 @@
<exclude name="output/**"/>
<exclude name="modules/**"/>
<exclude name="**/*.mdl"/>
<exclude name="**/*.svg"/>
<exclude name="**/*_2.xml"/>
<exclude name="res/checkstyle/header-al2.txt"/>
<!-- Exclude auto-generated files -->
......@@ -559,6 +560,8 @@
<exclude name="test/webapp/bug53257/**/*.txt"/>
<exclude name="test/webapp-fragments/WEB-INF/classes/*.txt"/>
<exclude name="test/webresources/**"/>
<!-- Exclude test files with unusual encodings -->
<exclude name="test/webapp/jsp/encoding/**"/>
</fileset>
<fileset dir="modules/jdbc-pool" >
<exclude name=".*/**"/>
......@@ -613,6 +616,10 @@
<exclude name="nbproject/**"/>
<exclude name="output/**"/>
<exclude name="modules/**"/>
<!-- Exclude these since some svn clients do not seem able to handle
UTF-16 line-ending conversion -->
<exclude name="test/webapp/jsp/encoding/bom-utf16*"/>
<exclude name="test/webapp/jsp/encoding/bom-none-prolog-utf16*"/>
</fileset>
<fileset dir="modules/jdbc-pool" >
<patternset refid="text.files" />
......@@ -1912,8 +1919,9 @@ Apache Tomcat ${version} native binaries for Win64 AMD64/EMT64 platform.
<!-- Copy deployer documentation -->
<copy todir="${tomcat.deployer}">
<fileset dir="${tomcat.build}/webapps/docs">
<include name="images/asf-logo.gif" />
<include name="images/tomcat.gif" />
<include name="images/asf-logo.svg" />
<include name="images/tomcat.png" />
<include name="images/docs-stylesheet.css" />
</fileset>
</copy>
<copy tofile="${tomcat.deployer}/deployer-howto.html"
......
......@@ -13,7 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
webappClassLoader.addExports=When running on Java 9 you need to add "--add-exports java.rmi/sun.rmi.transport=ALL-UNNAMED" to the JVM command line arguments to enable RMI Target memory leak detection. Alternatively, you can suppress this warning by disabling RMI Target memory leak detection.
webappClassLoader.addExportsRmi=When running on Java 9 you need to add "--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED" to the JVM command line arguments to enable RMI Target memory leak detection. Alternatively, you can suppress this warning by disabling RMI Target memory leak detection.
webappClassLoader.addExportsThreadLocal=When running on Java 9 you need to add "--add-opens=java.base/java.lang=ALL-UNNAMED" to the JVM command line arguments to enable ThreadLocal memory leak detection. Alternatively, you can suppress this warning by disabling ThreadLocal memory leak detection.
webappClassLoader.addPermisionNoCanonicalFile=Unable to obtain a canonical file path from the URL [{0}]
webappClassLoader.addPermisionNoProtocol=The protocol [{0}] in the URL [{1}] is not supported so no read permission was granted for resources located at this URL
webappClassLoader.illegalJarPath=Illegal JAR entry detected with name {0}
......
......@@ -1870,10 +1870,17 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.warn(sm.getString(
"webappClassLoader.checkThreadLocalsForLeaksFail",
getContextName()), t);
JreCompat jreCompat = JreCompat.getInstance();
if (jreCompat.isInstanceOfInaccessibleObjectException(t)) {
// Must be running on Java 9 without the necessary command line
// options.
log.warn(sm.getString("webappClassLoader.addExportsThreadLocal"));
} else {
ExceptionUtils.handleThrowable(t);
log.warn(sm.getString(
"webappClassLoader.checkThreadLocalsForLeaksFail",
getContextName()), t);
}
}
}
......@@ -2080,37 +2087,39 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
return;
}
// Iterate over the values in the table
if (objTable instanceof Map<?,?>) {
Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
while (iter.hasNext()) {
Object obj = iter.next();
Object cclObject = cclField.get(obj);
if (this == cclObject) {
iter.remove();
Object stubObject = stubField.get(obj);
log.error(sm.getString("webappClassLoader.clearRmi",
stubObject.getClass().getName(), stubObject));
synchronized (objTable) {
// Iterate over the values in the table
if (objTable instanceof Map<?,?>) {
Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
while (iter.hasNext()) {
Object obj = iter.next();
Object cclObject = cclField.get(obj);
if (this == cclObject) {
iter.remove();
Object stubObject = stubField.get(obj);
log.error(sm.getString("webappClassLoader.clearRmi",
stubObject.getClass().getName(), stubObject));
}
}
}
}
// Clear the implTable map
Field implTableField = objectTableClass.getDeclaredField("implTable");
implTableField.setAccessible(true);
Object implTable = implTableField.get(null);
if (implTable == null) {
return;
}
// Clear the implTable map
Field implTableField = objectTableClass.getDeclaredField("implTable");
implTableField.setAccessible(true);
Object implTable = implTableField.get(null);
if (implTable == null) {
return;
}
// Iterate over the values in the table
if (implTable instanceof Map<?,?>) {
Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
while (iter.hasNext()) {
Object obj = iter.next();
Object cclObject = cclField.get(obj);
if (this == cclObject) {
iter.remove();
// Iterate over the values in the table
if (implTable instanceof Map<?,?>) {
Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
while (iter.hasNext()) {
Object obj = iter.next();
Object cclObject = cclField.get(obj);
if (this == cclObject) {
iter.remove();
}
}
}
}
......@@ -2126,7 +2135,7 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
if (jreCompat.isInstanceOfInaccessibleObjectException(e)) {
// Must be running on Java 9 without the necessary command line
// options.
log.warn(sm.getString("webappClassLoader.addExports"));
log.warn(sm.getString("webappClassLoader.addExportsRmi"));
} else {
// Re-throw all other exceptions
throw e;
......
......@@ -118,13 +118,13 @@ public class Constants {
"<table cellspacing=\"4\" border=\"0\">\n" +
" <tr>\n" +
" <td colspan=\"2\">\n" +
" <a href=\"http://www.apache.org/\">\n" +
" <img border=\"0\" alt=\"The Apache Software Foundation\" align=\"left\"\n" +
" src=\"{0}/images/asf-logo.gif\">\n" +
" </a>\n" +
" <a href=\"http://tomcat.apache.org/\">\n" +
" <img border=\"0\" alt=\"The Tomcat Servlet/JSP Container\"\n" +
" align=\"right\" src=\"{0}/images/tomcat.gif\">\n" +
" align=\"left\" src=\"{0}/images/tomcat.gif\">\n" +
" </a>\n" +
" <a href=\"http://www.apache.org/\">\n" +
" <img border=\"0\" alt=\"The Apache Software Foundation\" align=\"right\"\n" +
" src=\"{0}/images/asf-logo.svg\" style=\"width: 266px; height: 83px;\">\n" +
" </a>\n" +
" </td>\n" +
" </tr>\n" +
......@@ -201,7 +201,7 @@ public class Constants {
HTML_TAIL_SECTION =
"<hr size=\"1\" noshade=\"noshade\">\n" +
"<center><font size=\"-1\" color=\"#525D76\">\n" +
" <em>Copyright &copy; 1999-2016, Apache Software Foundation</em>" +
" <em>Copyright &copy; 1999-2017, Apache Software Foundation</em>" +
"</font></center>\n" +
"\n" +
"</body>\n" +
......
......@@ -736,7 +736,7 @@ public final class HTMLManagerServlet extends ManagerServlet {
*/
@Override
public String getServletInfo() {
return "HTMLManagerServlet, Copyright (c) 1999-2016, The Apache Software Foundation";
return "HTMLManagerServlet, Copyright (c) 1999-2017, The Apache Software Foundation";
}
/**
......
......@@ -79,7 +79,7 @@ public class Constants {
public static final String HTML_TAIL_SECTION =
"<hr size=\"1\" noshade=\"noshade\">\n" +
"<center><font size=\"-1\" color=\"#525D76\">\n" +
" <em>Copyright &copy; 1999-2016, Apache Software Foundation</em>" +
" <em>Copyright &copy; 1999-2017, Apache Software Foundation</em>" +
"</font></center>\n" +
"\n" +
"</body>\n" +
......
......@@ -126,6 +126,13 @@ public abstract class DigestCredentialHandlerBase implements CredentialHandler {
String serverCredential = mutate(userCredential, salt, iterations);
// Failed to generate server credential from user credential. Points to
// a configuration issue. The root cause should have been logged in the
// mutate() method.
if (serverCredential == null) {
return null;
}
if (saltLength == 0 && iterations == 1) {
// Output the simple/old format for backwards compatibility
return serverCredential;
......@@ -155,6 +162,13 @@ public abstract class DigestCredentialHandlerBase implements CredentialHandler {
protected boolean matchesSaltIterationsEncoded(String inputCredentials,
String storedCredentials) {
if (storedCredentials == null) {
// Stored credentials are invalid
// This may be expected if nested credential handlers are being used
logInvalidStoredCredentials(storedCredentials);
return false;
}
int sep1 = storedCredentials.indexOf('$');
int sep2 = storedCredentials.indexOf('$', sep1 + 1);
......@@ -178,7 +192,13 @@ public abstract class DigestCredentialHandlerBase implements CredentialHandler {
return false;
}
String inputHexEncoded = mutate(inputCredentials, salt, iterations);
String inputHexEncoded = mutate(inputCredentials, salt, iterations,
HexUtils.fromHexString(storedHexEncoded).length * Byte.SIZE);
if (inputHexEncoded == null) {
// Failed to mutate user credentials. Automatic fail.
// Root cause should be logged by mutate()
return false;
}
return storedHexEncoded.equalsIgnoreCase(inputHexEncoded);
}
......@@ -204,7 +224,8 @@ public abstract class DigestCredentialHandlerBase implements CredentialHandler {
/**
* Generates the equivalent stored credentials for the given input
* credentials, salt and iterations.
* credentials, salt and iterations. If the algorithm requires a key length,
* the default will be used.
*
* @param inputCredentials User provided credentials
* @param salt Salt, if any
......@@ -214,10 +235,35 @@ public abstract class DigestCredentialHandlerBase implements CredentialHandler {
* stored credentials
*
* @return The equivalent stored credentials for the given input
* credentials
* credentials or <code>null</code> if the generation fails
*/
protected abstract String mutate(String inputCredentials, byte[] salt, int iterations);
/**
* Generates the equivalent stored credentials for the given input
* credentials, salt, iterations and key length. The default implementation
* calls ignores the key length and calls
* {@link #mutate(String, byte[], int)}. Sub-classes that use the key length
* should override this method.
*
* @param inputCredentials User provided credentials
* @param salt Salt, if any
* @param iterations Number of iterations of the algorithm associated
* with this CredentialHandler applied to the
* inputCredentials to generate the equivalent
* stored credentials
* @param keyLength Length of the produced digest in bits for
* implementations where it's applicable
*
* @return The equivalent stored credentials for the given input
* credentials or <code>null</code> if the generation fails
*/
protected String mutate(String inputCredentials, byte[] salt, int iterations, int keyLength) {
return mutate(inputCredentials, salt, iterations);
}
/**
* Set the algorithm used to convert input credentials to stored
* credentials.
......
......@@ -93,5 +93,6 @@ combinedRealm.realmStartFail=Failed to start "{0}" realm
lockOutRealm.authLockedUser=An attempt was made to authenticate the locked user "{0}"
lockOutRealm.removeWarning=User "{0}" was removed from the failed users cache after {1} seconds to keep the cache size within the limit set
credentialHandler.invalidStoredCredential=The invalid stored credential string [{0}] was provided by the Realm to match with the user provided credentials
credentialHandler.unableToMutateUserCredential=Failed to mutate user provided credentials. This typically means the CredentialHandler configuration is invalid
mdCredentialHandler.unknownEncoding=The encoding [{0}] is not supported so the current setting of [{1}] will still be used
pbeCredentialHandler.invalidKeySpec=Unable to generate a password based key
\ No newline at end of file
......@@ -148,6 +148,11 @@ public class MessageDigestCredentialHandler extends DigestCredentialHandlerBase
} else {
// Hex hashes should be compared case-insensitively
String userDigest = mutate(inputCredentials, null, 1);
if (userDigest == null) {
// Failed to mutate user credentials. Automatic fail.
// Root cause should be logged by mutate()
return false;
}
return storedCredentials.equalsIgnoreCase(userDigest);
}
}
......
......@@ -76,12 +76,17 @@ public class SecretKeyCredentialHandler extends DigestCredentialHandlerBase {
@Override
protected String mutate(String inputCredentials, byte[] salt, int iterations) {
KeySpec spec = new PBEKeySpec(inputCredentials.toCharArray(), salt, iterations, getKeyLength());
return mutate(inputCredentials, salt, iterations, getKeyLength());
}
@Override
protected String mutate(String inputCredentials, byte[] salt, int iterations, int keyLength) {
try {
KeySpec spec = new PBEKeySpec(inputCredentials.toCharArray(), salt, iterations, keyLength);
return HexUtils.toHexString(secretKeyFactory.generateSecret(spec).getEncoded());
} catch (InvalidKeySpecException e) {
log.warn("pbeCredentialHandler.invalidKeySpec", e);
} catch (InvalidKeySpecException | IllegalArgumentException e) {
log.warn(sm.getString("pbeCredentialHandler.invalidKeySpec"), e);
return null;
}
}
......
......@@ -59,8 +59,6 @@ stuckThreadDetectionValve.notifyStuckThreadInterrupted=Thread "{0}" (id={5}) has
# HTTP status reports
# All status codes registered with IANA can be found at
# http://www.iana.org/assignments/http-status-codes/http-status-codes.xml
# The list might be kept in sync with the one in
# org/apache/tomcat/util/http/res/LocalStrings.properties.
http.100=The client may continue.
http.101=The server is switching protocols according to the "Upgrade" header.
http.102=The server has accepted the complete request, but has not yet completed it.
......@@ -106,6 +104,7 @@ http.426=The request can only be completed after a protocol upgrade.
http.428=The request is required to be conditional.
http.429=The user has sent too many requests in a given amount of time.
http.431=The server refused this request because the request header fields are too large.
http.451=The server refused this request for legal reasons.
http.500=The server encountered an internal error that prevented it from fulfilling this request.
http.501=The server does not support the functionality needed to fulfill this request.
http.502=This server received an invalid response from a server it consulted when acting as a proxy or gateway.
......
......@@ -21,7 +21,6 @@ import java.util.Calendar;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.connector.Request;
import org.apache.tomcat.util.http.FastHttpDateFormat;
public class ResolverImpl extends Resolver {
......
......@@ -296,12 +296,16 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
void nextRequest() {
request.recycle();
// Copy leftover bytes to the beginning of the buffer
if (byteBuffer.remaining() > 0 && byteBuffer.position() > 0) {
byteBuffer.compact();
if (byteBuffer.position() > 0) {
if (byteBuffer.remaining() > 0) {
// Copy leftover bytes to the beginning of the buffer
byteBuffer.compact();
byteBuffer.flip();
} else {
// Reset position and limit to 0
byteBuffer.position(0).limit(0);
}
}
// Always reset pos to zero
byteBuffer.limit(byteBuffer.limit() - byteBuffer.position()).position(0);
// Recycle filters
for (int i = 0; i <= lastActiveFilter; i++) {
......
......@@ -38,7 +38,7 @@ public abstract class ConnectionSettingsBase<T extends Throwable> {
protected static final int MAX_HEADER_TABLE_SIZE = 1 << 16;
// Defaults
protected static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
protected static final int DEFAULT_HEADER_TABLE_SIZE = Hpack.DEFAULT_TABLE_SIZE;
protected static final boolean DEFAULT_ENABLE_PUSH = true;
protected static final long DEFAULT_MAX_CONCURRENT_STREAMS = UNLIMITED;
protected static final int DEFAULT_INITIAL_WINDOW_SIZE = (1 << 16) - 1;
......
......@@ -434,7 +434,11 @@ public class HPackHuffman {
//so we end up iterating twice
int length = 0;
for (int i = 0; i < toEncode.length(); ++i) {
byte c = (byte) toEncode.charAt(i);
char c = toEncode.charAt(i);
if (c > 255) {
throw new IllegalArgumentException(sm.getString("hpack.invalidCharacter",
Character.toString(c), Integer.valueOf(c)));
}
if(forceLowercase) {
c = Hpack.toLower(c);
}
......@@ -450,7 +454,7 @@ public class HPackHuffman {
int bytePos = 0;
byte currentBufferByte = 0;
for (int i = 0; i < toEncode.length(); ++i) {
byte c = (byte) toEncode.charAt(i);
char c = toEncode.charAt(i);
if(forceLowercase) {
c = Hpack.toLower(c);
}
......
......@@ -204,11 +204,11 @@ final class Hpack {
}
static byte toLower(byte b) {
if (b >= 'A' && b <= 'Z') {
return (byte) (b + LOWER_DIFF);
static char toLower(char c) {
if (c >= 'A' && c <= 'Z') {
return (char) (c + LOWER_DIFF);
}
return b;
return c;
}
private Hpack() {}
......
......@@ -95,7 +95,7 @@ public class HpackEncoder {
/**
* The maximum table size
*/
private int maxTableSize;
private int maxTableSize = Hpack.DEFAULT_TABLE_SIZE;
/**
* The current table size
......@@ -104,13 +104,8 @@ public class HpackEncoder {
private final HpackHeaderFunction hpackHeaderFunction;
public HpackEncoder(int maxTableSize, HpackHeaderFunction headerFunction) {
this.maxTableSize = maxTableSize;
this.hpackHeaderFunction = headerFunction;
}
public HpackEncoder(int maxTableSize) {
this(maxTableSize, DEFAULT_HEADER_FUNCTION);
HpackEncoder() {
this.hpackHeaderFunction = DEFAULT_HEADER_FUNCTION;
}
/**
......@@ -218,7 +213,7 @@ public class HpackEncoder {
target.put((byte) 0); //to use encodeInteger we need to place the first byte in the buffer.
Hpack.encodeInteger(target, headerName.length(), 7);
for (int j = 0; j < headerName.length(); ++j) {
target.put(Hpack.toLower((byte) headerName.charAt(j)));
target.put((byte) Hpack.toLower(headerName.charAt(j)));
}
}
......
......@@ -636,8 +636,10 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
private HpackEncoder getHpackEncoder() {
if (hpackEncoder == null) {
hpackEncoder = new HpackEncoder(remoteSettings.getHeaderTableSize());
hpackEncoder = new HpackEncoder();
}
// Ensure latest agreed table size is used
hpackEncoder.setMaxTableSize(remoteSettings.getHeaderTableSize());
return hpackEncoder;
}
......
......@@ -32,6 +32,7 @@ frameType.checkPayloadSize=Payload size of [{0}] is not valid for frame type [{1
frameType.checkStream=Invalid frame type [{0}]
hpack.integerEncodedOverTooManyOctets=HPACK variable length integer encoded over too many octets, max is {0}
hpack.invalidCharacter=The Unicode character [{0}] at code point [{1}] cannot be encoded as it is outside the permitted range of 0 to 255.
hpackdecoder.zeroNotValidHeaderTableIndex=Zero is not a valid header table index
......
......@@ -97,6 +97,9 @@ public class Stream extends AbstractStream implements HeaderEmitter {
this.coyoteResponse.setOutputBuffer(outputBuffer);
this.coyoteRequest.setResponse(coyoteResponse);
this.coyoteRequest.protocol().setString("HTTP/2.0");
if (this.coyoteRequest.getStartTime() < 0) {
this.coyoteRequest.setStartTime(System.currentTimeMillis());
}
}
......@@ -274,7 +277,7 @@ public class Stream extends AbstractStream implements HeaderEmitter {
String query = value.substring(queryStart + 1);
coyoteRequest.requestURI().setString(uri);
coyoteRequest.decodedURI().setString(coyoteRequest.getURLDecoder().convert(uri, false));
coyoteRequest.queryString().setString(coyoteRequest.getURLDecoder().convert(query, true));
coyoteRequest.queryString().setString(query);
}
break;
}
......
/*
* 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.jasper.compiler;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
/*
* The BoM detection is derived from:
* http://svn.us.apache.org/viewvc/tomcat/trunk/java/org/apache/jasper/xmlparser/XMLEncodingDetector.java?annotate=1742248
*/
class EncodingDetector {
private static final XMLInputFactory XML_INPUT_FACTORY;
static {
XML_INPUT_FACTORY = XMLInputFactory.newFactory();
}
private final BomResult bomResult;
private final String prologEncoding;
/*
* TODO: Refactor Jasper InputStream creation and handling so the
* InputStream passed to this method is buffered and therefore saves
* on multiple opening and re-opening of the same file.
*/
EncodingDetector(InputStream is) throws IOException {
// Keep buffer size to a minimum here. BoM will be no more than 4 bytes
// so that is the maximum we need to buffer
BufferedInputStream bis = new BufferedInputStream(is, 4);
bis.mark(4);
bomResult = processBom(bis);
// Reset the stream back to the start to allow the XML prolog detection
// to work. Skip any BoM we discovered.
bis.reset();
for (int i = 0; i < bomResult.skip; i++) {
is.read();
}
prologEncoding = getPrologEncoding(bis);
}
String getBomEncoding() {
return bomResult.encoding;
}
int getSkip() {
return bomResult.skip;
}
String getPrologEncoding() {
return prologEncoding;
}
private String getPrologEncoding(InputStream stream) {
String encoding = null;
try {
XMLStreamReader xmlStreamReader = XML_INPUT_FACTORY.createXMLStreamReader(stream);
encoding = xmlStreamReader.getCharacterEncodingScheme();
} catch (XMLStreamException e) {
// Ignore
}
return encoding;
}
private BomResult processBom(InputStream stream) {
// Read first four bytes (or as many are available) and determine
// encoding
try {
final byte[] b4 = new byte[4];
int count = 0;
int singleByteRead;
while (count < 4) {
singleByteRead = stream.read();
if (singleByteRead == -1) {
break;
}
b4[count] = (byte) singleByteRead;
count++;
}
return parseBom(b4, count);
} catch (IOException ioe) {
// Failed.
return new BomResult("UTF-8", 0);
}
}
private BomResult parseBom(byte[] b4, int count) {
if (count < 2) {