diff --git a/core/src/main/java/org/jboss/logmanager/ExtHandler.java b/core/src/main/java/org/jboss/logmanager/ExtHandler.java index 20806c43..beb4530b 100644 --- a/core/src/main/java/org/jboss/logmanager/ExtHandler.java +++ b/core/src/main/java/org/jboss/logmanager/ExtHandler.java @@ -21,7 +21,9 @@ import java.io.Flushable; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.security.Permission; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.logging.ErrorManager; import java.util.logging.Filter; @@ -39,11 +41,13 @@ */ public abstract class ExtHandler extends Handler implements AutoCloseable, Flushable { + private static final ErrorManager DEFAULT_ERROR_MANAGER = new OnlyOnceErrorManager(); private static final Permission CONTROL_PERMISSION = new LoggingPermission("control", null); + private volatile boolean autoFlush = true; private volatile boolean enabled = true; private volatile boolean closeChildren; - private static final ErrorManager DEFAULT_ERROR_MANAGER = new OnlyOnceErrorManager(); + private volatile Charset charset = Charset.defaultCharset(); /** * The sub-handlers for this handler. May only be updated using the {@link #handlersUpdater} atomic updater. The array @@ -314,10 +318,65 @@ public void setFilter(final Filter newFilter) throws SecurityException { super.setFilter(newFilter); } + /** + * Set the handler's character set by name. This is roughly equivalent to calling {@link #setCharset(Charset)} with + * the results of {@link Charset#forName(String)}. + * + * @param encoding the name of the encoding + * @throws SecurityException if a security manager is installed and the caller does not have the {@code "control" LoggingPermission} + * @throws UnsupportedEncodingException if no character set could be found for the encoding name + */ @Override public void setEncoding(final String encoding) throws SecurityException, UnsupportedEncodingException { + try { + setCharset(Charset.forName(encoding)); + } catch (IllegalArgumentException e) { + final UnsupportedEncodingException e2 = new UnsupportedEncodingException("Unable to set encoding to \"" + encoding + "\""); + e2.initCause(e); + throw e2; + } + } + + /** + * Get the name of the {@linkplain #getCharset() handler's character set}. + * + * @return the handler character set name + */ + @Override + public String getEncoding() { + return getCharset().name(); + } + + /** + * Set the handler's character set. If not set, the handler's character set is initialized to the platform default + * character set. + * + * @param charset the character set (must not be {@code null}) + * @throws SecurityException if a security manager is installed and the caller does not have the {@code "control" LoggingPermission} + */ + public void setCharset(final Charset charset) throws SecurityException { checkAccess(); - super.setEncoding(encoding); + setCharsetPrivate(charset); + } + + /** + * Set the handler's character set from within this handler. If not set, the handler's character set is initialized + * to the platform default character set. + * + * @param charset the character set (must not be {@code null}) + */ + protected void setCharsetPrivate(final Charset charset) throws SecurityException { + Objects.requireNonNull(charset, "charset"); + this.charset = charset; + } + + /** + * Get the handler's character set. + * + * @return the character set in use (not {@code null}) + */ + public Charset getCharset() { + return charset; } @Override diff --git a/core/src/main/java/org/jboss/logmanager/handlers/OutputStreamHandler.java b/core/src/main/java/org/jboss/logmanager/handlers/OutputStreamHandler.java index 775d5919..96885d05 100644 --- a/core/src/main/java/org/jboss/logmanager/handlers/OutputStreamHandler.java +++ b/core/src/main/java/org/jboss/logmanager/handlers/OutputStreamHandler.java @@ -22,7 +22,6 @@ import org.jboss.logmanager.formatters.Formatters; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; import java.io.Writer; import java.nio.charset.Charset; @@ -36,7 +35,6 @@ public class OutputStreamHandler extends WriterHandler { private OutputStream outputStream; - private Charset charset; /** * Construct a new instance with no formatter. @@ -65,29 +63,11 @@ public OutputStreamHandler(final OutputStream outputStream, final Formatter form setOutputStream(outputStream); } - /** - * Get the target encoding. - * - * @return the target encoding, or {@code null} if the platform default is being used - */ - public String getEncoding() { - synchronized (outputLock) { - return super.getEncoding(); - } - } - - /** - * Set the target encoding. - * - * @param encoding the new encoding - * @throws SecurityException if you do not have sufficient permission to invoke this operation - * @throws java.io.UnsupportedEncodingException if the specified encoding is not supported - */ - public void setEncoding(final String encoding) throws SecurityException, UnsupportedEncodingException { + @Override + protected void setCharsetPrivate(Charset charset) throws SecurityException { // superclass checks access synchronized (outputLock) { - charset = encoding == null ? null : Charset.forName(encoding); - super.setEncoding(encoding); + super.setCharsetPrivate(charset); // we only want to change the writer, not the output stream final OutputStream outputStream = this.outputStream; if (outputStream != null) { @@ -96,6 +76,12 @@ public void setEncoding(final String encoding) throws SecurityException, Unsuppo } } + public Charset getCharset() { + synchronized (outputLock) { + return super.getCharset(); + } + } + /** {@inheritDoc} Setting a writer will replace any target output stream. */ public void setWriter(final Writer writer) { synchronized (outputLock) { @@ -143,7 +129,6 @@ public void setOutputStream(final OutputStream outputStream) { private Writer getNewWriter(OutputStream newOutputStream) { if (newOutputStream == null) return null; final UninterruptibleOutputStream outputStream = new UninterruptibleOutputStream(new UncloseableOutputStream(newOutputStream)); - final Charset charset = this.charset; - return charset == null ? new OutputStreamWriter(outputStream) : new OutputStreamWriter(outputStream, charset); + return new OutputStreamWriter(outputStream, getCharset()); } } diff --git a/core/src/main/java/org/jboss/logmanager/handlers/WriterHandler.java b/core/src/main/java/org/jboss/logmanager/handlers/WriterHandler.java index db0e2b4a..2d31db5d 100644 --- a/core/src/main/java/org/jboss/logmanager/handlers/WriterHandler.java +++ b/core/src/main/java/org/jboss/logmanager/handlers/WriterHandler.java @@ -36,8 +36,16 @@ public class WriterHandler extends ExtHandler { protected final Object outputLock = new Object(); + private volatile boolean checkHeadEncoding = true; + private volatile boolean checkTailEncoding = true; private Writer writer; + /** + * Construct a new instance. + */ + public WriterHandler() { + } + /** {@inheritDoc} */ protected void doPublish(final ExtLogRecord record) { final String formatted; @@ -112,10 +120,59 @@ public void setWriter(final Writer writer) { } } + /** + * Determine whether head encoding checking is turned on. + * + * @return {@code true} to check and report head encoding problems, or {@code false} to ignore them + */ + public boolean isCheckHeadEncoding() { + return checkHeadEncoding; + } + + /** + * Establish whether head encoding checking is turned on. + * + * @param checkHeadEncoding {@code true} to check and report head encoding problems, or {@code false} to ignore them + * @return this handler + */ + public WriterHandler setCheckHeadEncoding(boolean checkHeadEncoding) { + this.checkHeadEncoding = checkHeadEncoding; + return this; + } + + /** + * Determine whether tail encoding checking is turned on. + * + * @return {@code true} to check and report tail encoding problems, or {@code false} to ignore them + */ + public boolean isCheckTailEncoding() { + return checkTailEncoding; + } + + /** + * Establish whether tail encoding checking is turned on. + * + * @param checkTailEncoding {@code true} to check and report tail encoding problems, or {@code false} to ignore them + * @return this handler + */ + public WriterHandler setCheckTailEncoding(boolean checkTailEncoding) { + this.checkTailEncoding = checkTailEncoding; + return this; + } + private void writeHead(final Writer writer) { try { final Formatter formatter = getFormatter(); - if (formatter != null) writer.write(formatter.getHead(this)); + if (formatter != null) { + final String head = formatter.getHead(this); + if (checkHeadEncoding) { + if (!getCharset().newEncoder().canEncode(head)) { + reportError("Section header cannot be encoded into charset \"" + getCharset().name() + "\"", null, ErrorManager.GENERIC_FAILURE); + return; + } + } + writer.write(head); + } } catch (Exception e) { reportError("Error writing section header", e, ErrorManager.WRITE_FAILURE); } @@ -124,7 +181,16 @@ private void writeHead(final Writer writer) { private void writeTail(final Writer writer) { try { final Formatter formatter = getFormatter(); - if (formatter != null) writer.write(formatter.getTail(this)); + if (formatter != null) { + final String tail = formatter.getTail(this); + if (checkTailEncoding) { + if (!getCharset().newEncoder().canEncode(tail)) { + reportError("Section tail cannot be encoded into charset \"" + getCharset().name() + "\"", null, ErrorManager.GENERIC_FAILURE); + return; + } + } + writer.write(tail); + } } catch (Exception ex) { reportError("Error writing section tail", ex, ErrorManager.WRITE_FAILURE); } diff --git a/ext/src/main/java/org/jboss/logmanager/ext/handlers/SyslogHandler.java b/ext/src/main/java/org/jboss/logmanager/ext/handlers/SyslogHandler.java index 3a1f2281..937a3adb 100644 --- a/ext/src/main/java/org/jboss/logmanager/ext/handlers/SyslogHandler.java +++ b/ext/src/main/java/org/jboss/logmanager/ext/handlers/SyslogHandler.java @@ -25,6 +25,7 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import java.text.DateFormatSymbols; import java.text.Normalizer; import java.text.Normalizer.Form; @@ -462,6 +463,7 @@ public SyslogHandler(final String serverHostname, final int port, final Facility * @throws IOException if an error occurs creating the UDP socket */ public SyslogHandler(final InetAddress serverAddress, final int port, final Facility facility, final SyslogType syslogType, final Protocol protocol, final String hostname) throws IOException { + setCharsetPrivate(StandardCharsets.UTF_8); this.serverAddress = serverAddress; this.port = port; this.facility = facility;