This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,701 @@
/*
* 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.
*/
// XXX TODO: Source code line length
// XXX TODO: More JavaDoc
// XXX Optional: Add support for com.sun.management specific mbean
// (http://docs.oracle.com/javase/7/docs/jre/api/management/extension/index.html)
// XXX Optional: Wire additional public static methods implemented here
// to the manager (think about manager access roles!)
// setLoggerLevel(),
// setVerboseClassLoading(),
// setThreadContentionMonitoringEnabled(),
// setThreadCpuTimeEnabled(),
// resetPeakThreadCount(),
// setVerboseGarbageCollection()
// gc(),
// resetPeakUsage(),
// setUsageThreshold(),
// setCollectionUsageThreshold()
package org.apache.tomcat.util;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.CompilationMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryManagerMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.MonitorInfo;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.PlatformLoggingMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
public class Diagnostics {
private static final String PACKAGE = "org.apache.tomcat.util";
private static final StringManager sm = StringManager.getManager(PACKAGE);
private static final String INDENT1 = " ";
private static final String INDENT2 = "\t";
private static final String INDENT3 = " ";
private static final String CRLF = "\r\n";
private static final String vminfoSystemProperty = "java.vm.info";
private static final Log log = LogFactory.getLog(Diagnostics.class);
private static final SimpleDateFormat timeformat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
/* Some platform MBeans */
private static final ClassLoadingMXBean classLoadingMXBean =
ManagementFactory.getClassLoadingMXBean();
private static final CompilationMXBean compilationMXBean =
ManagementFactory.getCompilationMXBean();
private static final OperatingSystemMXBean operatingSystemMXBean =
ManagementFactory.getOperatingSystemMXBean();
private static final RuntimeMXBean runtimeMXBean =
ManagementFactory.getRuntimeMXBean();
private static final ThreadMXBean threadMXBean =
ManagementFactory.getThreadMXBean();
// XXX Not sure whether the following MBeans should better
// be retrieved on demand, i.e. whether they can change
// dynamically in the MBeanServer.
private static final PlatformLoggingMXBean loggingMXBean =
ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class);
private static final MemoryMXBean memoryMXBean =
ManagementFactory.getMemoryMXBean();
private static final List<GarbageCollectorMXBean> garbageCollectorMXBeans =
ManagementFactory.getGarbageCollectorMXBeans();
private static final List<MemoryManagerMXBean> memoryManagerMXBeans =
ManagementFactory.getMemoryManagerMXBeans();
private static final List<MemoryPoolMXBean> memoryPoolMXBeans =
ManagementFactory.getMemoryPoolMXBeans();
/**
* Check whether thread contention monitoring is enabled.
*
* @return true if thread contention monitoring is enabled
*/
public static boolean isThreadContentionMonitoringEnabled() {
return threadMXBean.isThreadContentionMonitoringEnabled();
}
/**
* Enable or disable thread contention monitoring via the ThreadMxMXBean.
*
* @param enable whether to enable thread contention monitoring
*/
public static void setThreadContentionMonitoringEnabled(boolean enable) {
threadMXBean.setThreadContentionMonitoringEnabled(enable);
boolean checkValue = threadMXBean.isThreadContentionMonitoringEnabled();
if (enable != checkValue) {
log.error("Could not set threadContentionMonitoringEnabled to " +
enable + ", got " + checkValue + " instead");
}
}
/**
* Check whether thread cpu time measurement is enabled.
*
* @return true if thread cpu time measurement is enabled
*/
public static boolean isThreadCpuTimeEnabled() {
return threadMXBean.isThreadCpuTimeEnabled();
}
/**
* Enable or disable thread cpu time measurement via the ThreadMxMXBean.
*
* @param enable whether to enable thread cpu time measurement
*/
public static void setThreadCpuTimeEnabled(boolean enable) {
threadMXBean.setThreadCpuTimeEnabled(enable);
boolean checkValue = threadMXBean.isThreadCpuTimeEnabled();
if (enable != checkValue) {
log.error("Could not set threadCpuTimeEnabled to " + enable +
", got " + checkValue + " instead");
}
}
/**
* Reset peak thread count in ThreadMXBean
*/
public static void resetPeakThreadCount() {
threadMXBean.resetPeakThreadCount();
}
/**
* Set verbose class loading
*
* @param verbose whether to enable verbose class loading
*/
public static void setVerboseClassLoading(boolean verbose) {
classLoadingMXBean.setVerbose(verbose);
boolean checkValue = classLoadingMXBean.isVerbose();
if (verbose != checkValue) {
log.error("Could not set verbose class loading to " + verbose +
", got " + checkValue + " instead");
}
}
/**
* Set logger level
*
* @param loggerName the name of the logger
* @param levelName the level to set
*/
public static void setLoggerLevel(String loggerName, String levelName) {
loggingMXBean.setLoggerLevel(loggerName, levelName);
String checkValue = loggingMXBean.getLoggerLevel(loggerName);
if (!checkValue.equals(levelName)) {
log.error("Could not set logger level for logger '" +
loggerName + "' to '" + levelName +
"', got '" + checkValue + "' instead");
}
}
/**
* Set verbose garbage collection logging
*
* @param verbose whether to enable verbose gc logging
*/
public static void setVerboseGarbageCollection(boolean verbose) {
memoryMXBean.setVerbose(verbose);
boolean checkValue = memoryMXBean.isVerbose();
if (verbose != checkValue) {
log.error("Could not set verbose garbage collection logging to " + verbose +
", got " + checkValue + " instead");
}
}
/**
* Initiate garbage collection via MX Bean
*/
public static void gc() {
memoryMXBean.gc();
}
/**
* Reset peak memory usage data in MemoryPoolMXBean
*
* @param name name of the MemoryPoolMXBean or "all"
*/
public static void resetPeakUsage(String name) {
for (MemoryPoolMXBean mbean: memoryPoolMXBeans) {
if (name.equals("all") || name.equals(mbean.getName())) {
mbean.resetPeakUsage();
}
}
}
/**
* Set usage threshold in MemoryPoolMXBean
*
* @param name name of the MemoryPoolMXBean
* @param threshold the threshold to set
* @return true if setting the threshold succeeded
*/
public static boolean setUsageThreshold(String name, long threshold) {
for (MemoryPoolMXBean mbean: memoryPoolMXBeans) {
if (name.equals(mbean.getName())) {
try {
mbean.setUsageThreshold(threshold);
return true;
} catch (IllegalArgumentException ex) {
// IGNORE
} catch (UnsupportedOperationException ex) {
// IGNORE
}
return false;
}
}
return false;
}
/**
* Set collection usage threshold in MemoryPoolMXBean
*
* @param name name of the MemoryPoolMXBean
* @param threshold the collection threshold to set
* @return true if setting the threshold succeeded
*/
public static boolean setCollectionUsageThreshold(String name, long threshold) {
for (MemoryPoolMXBean mbean: memoryPoolMXBeans) {
if (name.equals(mbean.getName())) {
try {
mbean.setCollectionUsageThreshold(threshold);
return true;
} catch (IllegalArgumentException ex) {
// IGNORE
} catch (UnsupportedOperationException ex) {
// IGNORE
}
return false;
}
}
return false;
}
/**
* Formats the thread dump header for one thread.
*
* @param ti the ThreadInfo describing the thread
* @return the formatted thread dump header
*/
private static String getThreadDumpHeader(ThreadInfo ti) {
StringBuilder sb = new StringBuilder("\"" + ti.getThreadName() + "\"");
sb.append(" Id=" + ti.getThreadId());
sb.append(" cpu=" + threadMXBean.getThreadCpuTime(ti.getThreadId()) +
" ns");
sb.append(" usr=" + threadMXBean.getThreadUserTime(ti.getThreadId()) +
" ns");
sb.append(" blocked " + ti.getBlockedCount() + " for " +
ti.getBlockedTime() + " ms");
sb.append(" waited " + ti.getWaitedCount() + " for " +
ti.getWaitedTime() + " ms");
if (ti.isSuspended()) {
sb.append(" (suspended)");
}
if (ti.isInNative()) {
sb.append(" (running in native)");
}
sb.append(CRLF);
sb.append(INDENT3 + "java.lang.Thread.State: " + ti.getThreadState());
sb.append(CRLF);
return sb.toString();
}
/**
* Formats the thread dump for one thread.
*
* @param ti the ThreadInfo describing the thread
* @return the formatted thread dump
*/
private static String getThreadDump(ThreadInfo ti) {
StringBuilder sb = new StringBuilder(getThreadDumpHeader(ti));
for (LockInfo li : ti.getLockedSynchronizers()) {
sb.append(INDENT2 + "locks " +
li.toString() + CRLF);
}
boolean start = true;
StackTraceElement[] stes = ti.getStackTrace();
Object[] monitorDepths = new Object[stes.length];
MonitorInfo[] mis = ti.getLockedMonitors();
for (int i = 0; i < mis.length; i++) {
monitorDepths[mis[i].getLockedStackDepth()] = mis[i];
}
for (int i = 0; i < stes.length; i++) {
StackTraceElement ste = stes[i];
sb.append(INDENT2 +
"at " + ste.toString() + CRLF);
if (start) {
if (ti.getLockName() != null) {
sb.append(INDENT2 + "- waiting on (a " +
ti.getLockName() + ")");
if (ti.getLockOwnerName() != null) {
sb.append(" owned by " + ti.getLockOwnerName() +
" Id=" + ti.getLockOwnerId());
}
sb.append(CRLF);
}
start = false;
}
if (monitorDepths[i] != null) {
MonitorInfo mi = (MonitorInfo)monitorDepths[i];
sb.append(INDENT2 +
"- locked (a " + mi.toString() + ")"+
" index " + mi.getLockedStackDepth() +
" frame " + mi.getLockedStackFrame().toString());
sb.append(CRLF);
}
}
return sb.toString();
}
/**
* Formats the thread dump for a list of threads.
*
* @param tinfos the ThreadInfo array describing the thread list
* @return the formatted thread dump
*/
private static String getThreadDump(ThreadInfo[] tinfos) {
StringBuilder sb = new StringBuilder();
for (ThreadInfo tinfo : tinfos) {
sb.append(getThreadDump(tinfo));
sb.append(CRLF);
}
return sb.toString();
}
/**
* Check if any threads are deadlocked. If any, print
* the thread dump for those threads.
*
* @return a deadlock message and the formatted thread dump
* of the deadlocked threads
*/
public static String findDeadlock() {
ThreadInfo[] tinfos = null;
long[] ids = threadMXBean.findDeadlockedThreads();
if (ids != null) {
tinfos = threadMXBean.getThreadInfo(threadMXBean.findDeadlockedThreads(),
true, true);
if (tinfos != null) {
StringBuilder sb =
new StringBuilder("Deadlock found between the following threads:");
sb.append(CRLF);
sb.append(getThreadDump(tinfos));
return sb.toString();
}
}
return "";
}
/**
* Retrieves a formatted JVM thread dump.
* The default StringManager will be used.
*
* @return the formatted JVM thread dump
*/
public static String getThreadDump() {
return getThreadDump(sm);
}
/**
* Retrieves a formatted JVM thread dump.
* The given list of locales will be used
* to retrieve a StringManager.
*
* @param requestedLocales list of locales to use
* @return the formatted JVM thread dump
*/
public static String getThreadDump(Enumeration<Locale> requestedLocales) {
return getThreadDump(
StringManager.getManager(PACKAGE, requestedLocales));
}
/**
* Retrieve a JVM thread dump formatted
* using the given StringManager.
*
* @param requestedSm the StringManager to use
* @return the formatted JVM thread dump
*/
public static String getThreadDump(StringManager requestedSm) {
StringBuilder sb = new StringBuilder();
synchronized(timeformat) {
sb.append(timeformat.format(new Date()));
}
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.threadDumpTitle"));
sb.append(" ");
sb.append(runtimeMXBean.getVmName());
sb.append(" (");
sb.append(runtimeMXBean.getVmVersion());
String vminfo = System.getProperty(vminfoSystemProperty);
if (vminfo != null) {
sb.append(" " + vminfo);
}
sb.append("):" + CRLF);
sb.append(CRLF);
ThreadInfo[] tis = threadMXBean.dumpAllThreads(true, true);
sb.append(getThreadDump(tis));
sb.append(findDeadlock());
return sb.toString();
}
/**
* Format contents of a MemoryUsage object.
* @param name a text prefix used in formatting
* @param usage the MemoryUsage object to format
* @return the formatted contents
*/
private static String formatMemoryUsage(String name, MemoryUsage usage) {
if (usage != null) {
StringBuilder sb = new StringBuilder();
sb.append(INDENT1 + name + " init: " + usage.getInit() + CRLF);
sb.append(INDENT1 + name + " used: " + usage.getUsed() + CRLF);
sb.append(INDENT1 + name + " committed: " + usage.getCommitted() + CRLF);
sb.append(INDENT1 + name + " max: " + usage.getMax() + CRLF);
return sb.toString();
}
return "";
}
/**
* Retrieves a formatted JVM information text.
* The default StringManager will be used.
*
* @return the formatted JVM information text
*/
public static String getVMInfo() {
return getVMInfo(sm);
}
/**
* Retrieves a formatted JVM information text.
* The given list of locales will be used
* to retrieve a StringManager.
*
* @param requestedLocales list of locales to use
* @return the formatted JVM information text
*/
public static String getVMInfo(Enumeration<Locale> requestedLocales) {
return getVMInfo(StringManager.getManager(PACKAGE, requestedLocales));
}
/**
* Retrieve a JVM information text formatted
* using the given StringManager.
*
* @param requestedSm the StringManager to use
* @return the formatted JVM information text
*/
public static String getVMInfo(StringManager requestedSm) {
StringBuilder sb = new StringBuilder();
synchronized(timeformat) {
sb.append(timeformat.format(new Date()));
}
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoRuntime"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "vmName: " + runtimeMXBean.getVmName() + CRLF);
sb.append(INDENT1 + "vmVersion: " + runtimeMXBean.getVmVersion() + CRLF);
sb.append(INDENT1 + "vmVendor: " + runtimeMXBean.getVmVendor() + CRLF);
sb.append(INDENT1 + "specName: " + runtimeMXBean.getSpecName() + CRLF);
sb.append(INDENT1 + "specVersion: " + runtimeMXBean.getSpecVersion() + CRLF);
sb.append(INDENT1 + "specVendor: " + runtimeMXBean.getSpecVendor() + CRLF);
sb.append(INDENT1 + "managementSpecVersion: " +
runtimeMXBean.getManagementSpecVersion() + CRLF);
sb.append(INDENT1 + "name: " + runtimeMXBean.getName() + CRLF);
sb.append(INDENT1 + "startTime: " + runtimeMXBean.getStartTime() + CRLF);
sb.append(INDENT1 + "uptime: " + runtimeMXBean.getUptime() + CRLF);
sb.append(INDENT1 + "isBootClassPathSupported: " +
runtimeMXBean.isBootClassPathSupported() + CRLF);
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoOs"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "name: " + operatingSystemMXBean.getName() + CRLF);
sb.append(INDENT1 + "version: " + operatingSystemMXBean.getVersion() + CRLF);
sb.append(INDENT1 + "architecture: " + operatingSystemMXBean.getArch() + CRLF);
sb.append(INDENT1 + "availableProcessors: " +
operatingSystemMXBean.getAvailableProcessors() + CRLF);
sb.append(INDENT1 + "systemLoadAverage: " +
operatingSystemMXBean.getSystemLoadAverage() + CRLF);
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoThreadMxBean"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "isCurrentThreadCpuTimeSupported: " +
threadMXBean.isCurrentThreadCpuTimeSupported() + CRLF);
sb.append(INDENT1 + "isThreadCpuTimeSupported: " +
threadMXBean.isThreadCpuTimeSupported() + CRLF);
sb.append(INDENT1 + "isThreadCpuTimeEnabled: " +
threadMXBean.isThreadCpuTimeEnabled() + CRLF);
sb.append(INDENT1 + "isObjectMonitorUsageSupported: " +
threadMXBean.isObjectMonitorUsageSupported() + CRLF);
sb.append(INDENT1 + "isSynchronizerUsageSupported: " +
threadMXBean.isSynchronizerUsageSupported() + CRLF);
sb.append(INDENT1 + "isThreadContentionMonitoringSupported: " +
threadMXBean.isThreadContentionMonitoringSupported() + CRLF);
sb.append(INDENT1 + "isThreadContentionMonitoringEnabled: " +
threadMXBean.isThreadContentionMonitoringEnabled() + CRLF);
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoThreadCounts"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "daemon: " + threadMXBean.getDaemonThreadCount() + CRLF);
sb.append(INDENT1 + "total: " + threadMXBean.getThreadCount() + CRLF);
sb.append(INDENT1 + "peak: " + threadMXBean.getPeakThreadCount() + CRLF);
sb.append(INDENT1 + "totalStarted: " +
threadMXBean.getTotalStartedThreadCount() + CRLF);
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoStartup"));
sb.append(":" + CRLF);
for (String arg: runtimeMXBean.getInputArguments()) {
sb.append(INDENT1 + arg + CRLF);
}
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoPath"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "bootClassPath: " + runtimeMXBean.getBootClassPath() + CRLF);
sb.append(INDENT1 + "classPath: " + runtimeMXBean.getClassPath() + CRLF);
sb.append(INDENT1 + "libraryPath: " + runtimeMXBean.getLibraryPath() + CRLF);
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoClassLoading"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "loaded: " +
classLoadingMXBean.getLoadedClassCount() + CRLF);
sb.append(INDENT1 + "unloaded: " +
classLoadingMXBean.getUnloadedClassCount() + CRLF);
sb.append(INDENT1 + "totalLoaded: " +
classLoadingMXBean.getTotalLoadedClassCount() + CRLF);
sb.append(INDENT1 + "isVerbose: " +
classLoadingMXBean.isVerbose() + CRLF);
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoClassCompilation"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "name: " + compilationMXBean.getName() + CRLF);
sb.append(INDENT1 + "totalCompilationTime: " +
compilationMXBean.getTotalCompilationTime() + CRLF);
sb.append(INDENT1 + "isCompilationTimeMonitoringSupported: " +
compilationMXBean.isCompilationTimeMonitoringSupported() + CRLF);
sb.append(CRLF);
for (MemoryManagerMXBean mbean: memoryManagerMXBeans) {
sb.append(requestedSm.getString("diagnostics.vmInfoMemoryManagers", mbean.getName()));
sb.append(":" + CRLF);
sb.append(INDENT1 + "isValid: " + mbean.isValid() + CRLF);
sb.append(INDENT1 + "mbean.getMemoryPoolNames: " + CRLF);
String[] names = mbean.getMemoryPoolNames();
Arrays.sort(names);
for (String name: names) {
sb.append(INDENT2 + name + CRLF);
}
sb.append(CRLF);
}
for (GarbageCollectorMXBean mbean: garbageCollectorMXBeans) {
sb.append(requestedSm.getString("diagnostics.vmInfoGarbageCollectors", mbean.getName()));
sb.append(":" + CRLF);
sb.append(INDENT1 + "isValid: " + mbean.isValid() + CRLF);
sb.append(INDENT1 + "mbean.getMemoryPoolNames: " + CRLF);
String[] names = mbean.getMemoryPoolNames();
Arrays.sort(names);
for (String name: names) {
sb.append(INDENT2 + name + CRLF);
}
sb.append(INDENT1 + "getCollectionCount: " + mbean.getCollectionCount() + CRLF);
sb.append(INDENT1 + "getCollectionTime: " + mbean.getCollectionTime() + CRLF);
sb.append(CRLF);
}
sb.append(requestedSm.getString("diagnostics.vmInfoMemory"));
sb.append(":" + CRLF);
sb.append(INDENT1 + "isVerbose: " + memoryMXBean.isVerbose() + CRLF);
sb.append(INDENT1 + "getObjectPendingFinalizationCount: " + memoryMXBean.getObjectPendingFinalizationCount() + CRLF);
sb.append(formatMemoryUsage("heap", memoryMXBean.getHeapMemoryUsage()));
sb.append(formatMemoryUsage("non-heap", memoryMXBean.getNonHeapMemoryUsage()));
sb.append(CRLF);
for (MemoryPoolMXBean mbean: memoryPoolMXBeans) {
sb.append(requestedSm.getString("diagnostics.vmInfoMemoryPools", mbean.getName()));
sb.append(":" + CRLF);
sb.append(INDENT1 + "isValid: " + mbean.isValid() + CRLF);
sb.append(INDENT1 + "getType: " + mbean.getType() + CRLF);
sb.append(INDENT1 + "mbean.getMemoryManagerNames: " + CRLF);
String[] names = mbean.getMemoryManagerNames();
Arrays.sort(names);
for (String name: names) {
sb.append(INDENT2 + name + CRLF);
}
sb.append(INDENT1 + "isUsageThresholdSupported: " + mbean.isUsageThresholdSupported() + CRLF);
try {
sb.append(INDENT1 + "isUsageThresholdExceeded: " + mbean.isUsageThresholdExceeded() + CRLF);
} catch (UnsupportedOperationException ex) {
// IGNORE
}
sb.append(INDENT1 + "isCollectionUsageThresholdSupported: " + mbean.isCollectionUsageThresholdSupported() + CRLF);
try {
sb.append(INDENT1 + "isCollectionUsageThresholdExceeded: " + mbean.isCollectionUsageThresholdExceeded() + CRLF);
} catch (UnsupportedOperationException ex) {
// IGNORE
}
try {
sb.append(INDENT1 + "getUsageThreshold: " + mbean.getUsageThreshold() + CRLF);
} catch (UnsupportedOperationException ex) {
// IGNORE
}
try {
sb.append(INDENT1 + "getUsageThresholdCount: " + mbean.getUsageThresholdCount() + CRLF);
} catch (UnsupportedOperationException ex) {
// IGNORE
}
try {
sb.append(INDENT1 + "getCollectionUsageThreshold: " + mbean.getCollectionUsageThreshold() + CRLF);
} catch (UnsupportedOperationException ex) {
// IGNORE
}
try {
sb.append(INDENT1 + "getCollectionUsageThresholdCount: " + mbean.getCollectionUsageThresholdCount() + CRLF);
} catch (UnsupportedOperationException ex) {
// IGNORE
}
sb.append(formatMemoryUsage("current", mbean.getUsage()));
sb.append(formatMemoryUsage("collection", mbean.getCollectionUsage()));
sb.append(formatMemoryUsage("peak", mbean.getPeakUsage()));
sb.append(CRLF);
}
sb.append(requestedSm.getString("diagnostics.vmInfoSystem"));
sb.append(":" + CRLF);
Map<String,String> props = runtimeMXBean.getSystemProperties();
ArrayList<String> keys = new ArrayList<>(props.keySet());
Collections.sort(keys);
for (String prop: keys) {
sb.append(INDENT1 + prop + ": " + props.get(prop) + CRLF);
}
sb.append(CRLF);
sb.append(requestedSm.getString("diagnostics.vmInfoLogger"));
sb.append(":" + CRLF);
List<String> loggers = loggingMXBean.getLoggerNames();
Collections.sort(loggers);
for (String logger: loggers) {
sb.append(INDENT1 + logger +
": level=" + loggingMXBean.getLoggerLevel(logger) +
", parent=" + loggingMXBean.getParentLoggerName(logger) + CRLF);
}
sb.append(CRLF);
return sb.toString();
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.tomcat.util;
import java.lang.reflect.InvocationTargetException;
/**
* Utilities for handling Throwables and Exceptions.
*/
public class ExceptionUtils {
/**
* Checks whether the supplied Throwable is one that needs to be
* rethrown and swallows all others.
* @param t the Throwable to check
*/
public static void handleThrowable(Throwable t) {
if (t instanceof ThreadDeath) {
throw (ThreadDeath) t;
}
if (t instanceof StackOverflowError) {
// Swallow silently - it should be recoverable
return;
}
if (t instanceof VirtualMachineError) {
throw (VirtualMachineError) t;
}
// All other instances of Throwable will be silently swallowed
}
/**
* Checks whether the supplied Throwable is an instance of
* <code>InvocationTargetException</code> and returns the throwable that is
* wrapped by it, if there is any.
*
* @param t the Throwable to check
* @return <code>t</code> or <code>t.getCause()</code>
*/
public static Throwable unwrapInvocationTargetException(Throwable t) {
if (t instanceof InvocationTargetException && t.getCause() != null) {
return t.getCause();
}
return t;
}
/**
* NO-OP method provided to enable simple pre-loading of this class. Since
* the class is used extensively in error handling, it is prudent to
* pre-load it to avoid any failure to load this class masking the true
* problem during error handling.
*/
public static void preload() {
// NO-OP
}
}

View File

@@ -0,0 +1,538 @@
/*
* 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.tomcat.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Hashtable;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.security.PermissionCheck;
/**
* Utils for introspection and reflection
*/
public final class IntrospectionUtils {
private static final Log log = LogFactory.getLog(IntrospectionUtils.class);
/**
* Find a method with the right name If found, call the method ( if param is
* int or boolean we'll convert value to the right type before) - that means
* you can have setDebug(1).
* @param o The object to set a property on
* @param name The property name
* @param value The property value
* @return <code>true</code> if operation was successful
*/
public static boolean setProperty(Object o, String name, String value) {
return setProperty(o,name,value,true);
}
@SuppressWarnings("null") // setPropertyMethodVoid is not null when used
public static boolean setProperty(Object o, String name, String value,
boolean invokeSetProperty) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: setProperty(" +
o.getClass() + " " + name + "=" + value + ")");
String setter = "set" + capitalize(name);
try {
Method methods[] = findMethods(o.getClass());
Method setPropertyMethodVoid = null;
Method setPropertyMethodBool = null;
// First, the ideal case - a setFoo( String ) method
for (int i = 0; i < methods.length; i++) {
Class<?> paramT[] = methods[i].getParameterTypes();
if (setter.equals(methods[i].getName()) && paramT.length == 1
&& "java.lang.String".equals(paramT[0].getName())) {
methods[i].invoke(o, new Object[] { value });
return true;
}
}
// Try a setFoo ( int ) or ( boolean )
for (int i = 0; i < methods.length; i++) {
boolean ok = true;
if (setter.equals(methods[i].getName())
&& methods[i].getParameterTypes().length == 1) {
// match - find the type and invoke it
Class<?> paramType = methods[i].getParameterTypes()[0];
Object params[] = new Object[1];
// Try a setFoo ( int )
if ("java.lang.Integer".equals(paramType.getName())
|| "int".equals(paramType.getName())) {
try {
params[0] = Integer.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( long )
}else if ("java.lang.Long".equals(paramType.getName())
|| "long".equals(paramType.getName())) {
try {
params[0] = Long.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( boolean )
} else if ("java.lang.Boolean".equals(paramType.getName())
|| "boolean".equals(paramType.getName())) {
params[0] = Boolean.valueOf(value);
// Try a setFoo ( InetAddress )
} else if ("java.net.InetAddress".equals(paramType
.getName())) {
try {
params[0] = InetAddress.getByName(value);
} catch (UnknownHostException exc) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
ok = false;
}
// Unknown type
} else {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unknown type " +
paramType.getName());
}
if (ok) {
methods[i].invoke(o, params);
return true;
}
}
// save "setProperty" for later
if ("setProperty".equals(methods[i].getName())) {
if (methods[i].getReturnType()==Boolean.TYPE){
setPropertyMethodBool = methods[i];
}else {
setPropertyMethodVoid = methods[i];
}
}
}
// Ok, no setXXX found, try a setProperty("name", "value")
if (invokeSetProperty && (setPropertyMethodBool != null ||
setPropertyMethodVoid != null)) {
Object params[] = new Object[2];
params[0] = name;
params[1] = value;
if (setPropertyMethodBool != null) {
try {
return ((Boolean) setPropertyMethodBool.invoke(o,
params)).booleanValue();
}catch (IllegalArgumentException biae) {
//the boolean method had the wrong
//parameter types. lets try the other
if (setPropertyMethodVoid!=null) {
setPropertyMethodVoid.invoke(o, params);
return true;
}else {
throw biae;
}
}
} else {
setPropertyMethodVoid.invoke(o, params);
return true;
}
}
} catch (IllegalArgumentException ex2) {
log.warn("IAE " + o + " " + name + " " + value, ex2);
} catch (SecurityException ex1) {
log.warn("IntrospectionUtils: SecurityException for " +
o.getClass() + " " + name + "=" + value + ")", ex1);
} catch (IllegalAccessException iae) {
log.warn("IntrospectionUtils: IllegalAccessException for " +
o.getClass() + " " + name + "=" + value + ")", iae);
} catch (InvocationTargetException ie) {
ExceptionUtils.handleThrowable(ie.getCause());
log.warn("IntrospectionUtils: InvocationTargetException for " +
o.getClass() + " " + name + "=" + value + ")", ie);
}
return false;
}
public static Object getProperty(Object o, String name) {
String getter = "get" + capitalize(name);
String isGetter = "is" + capitalize(name);
try {
Method methods[] = findMethods(o.getClass());
Method getPropertyMethod = null;
// First, the ideal case - a getFoo() method
for (int i = 0; i < methods.length; i++) {
Class<?> paramT[] = methods[i].getParameterTypes();
if (getter.equals(methods[i].getName()) && paramT.length == 0) {
return methods[i].invoke(o, (Object[]) null);
}
if (isGetter.equals(methods[i].getName()) && paramT.length == 0) {
return methods[i].invoke(o, (Object[]) null);
}
if ("getProperty".equals(methods[i].getName())) {
getPropertyMethod = methods[i];
}
}
// Ok, no setXXX found, try a getProperty("name")
if (getPropertyMethod != null) {
Object params[] = new Object[1];
params[0] = name;
return getPropertyMethod.invoke(o, params);
}
} catch (IllegalArgumentException ex2) {
log.warn("IAE " + o + " " + name, ex2);
} catch (SecurityException ex1) {
log.warn("IntrospectionUtils: SecurityException for " +
o.getClass() + " " + name + ")", ex1);
} catch (IllegalAccessException iae) {
log.warn("IntrospectionUtils: IllegalAccessException for " +
o.getClass() + " " + name + ")", iae);
} catch (InvocationTargetException ie) {
if (ie.getCause() instanceof NullPointerException) {
// Assume the underlying object uses a storage to represent an unset property
return null;
}
ExceptionUtils.handleThrowable(ie.getCause());
log.warn("IntrospectionUtils: InvocationTargetException for " +
o.getClass() + " " + name + ")", ie);
}
return null;
}
/**
* Replace ${NAME} with the property value.
* @param value The value
* @param staticProp Replacement properties
* @param dynamicProp Replacement properties
* @return the replacement value
* @deprecated Use {@link #replaceProperties(String, Hashtable, PropertySource[], ClassLoader)}
*/
@Deprecated
public static String replaceProperties(String value,
Hashtable<Object,Object> staticProp, PropertySource dynamicProp[]) {
return replaceProperties(value, staticProp, dynamicProp, null);
}
/**
* Replace ${NAME} with the property value.
* @param value The value
* @param staticProp Replacement properties
* @param dynamicProp Replacement properties
* @param classLoader Class loader associated with the code requesting the
* property
* @return the replacement value
*/
public static String replaceProperties(String value,
Hashtable<Object,Object> staticProp, PropertySource dynamicProp[],
ClassLoader classLoader) {
if (value.indexOf('$') < 0) {
return value;
}
StringBuilder sb = new StringBuilder();
int prev = 0;
// assert value!=nil
int pos;
while ((pos = value.indexOf('$', prev)) >= 0) {
if (pos > 0) {
sb.append(value.substring(prev, pos));
}
if (pos == (value.length() - 1)) {
sb.append('$');
prev = pos + 1;
} else if (value.charAt(pos + 1) != '{') {
sb.append('$');
prev = pos + 1; // XXX
} else {
int endName = value.indexOf('}', pos);
if (endName < 0) {
sb.append(value.substring(pos));
prev = value.length();
continue;
}
String n = value.substring(pos + 2, endName);
String v = null;
if (staticProp != null) {
v = (String) staticProp.get(n);
}
if (v == null && dynamicProp != null) {
for (PropertySource propertySource : dynamicProp) {
if (propertySource instanceof SecurePropertySource) {
v = ((SecurePropertySource) propertySource).getProperty(n, classLoader);
} else {
v = propertySource.getProperty(n);
}
if (v != null) {
break;
}
}
}
if (v == null)
v = "${" + n + "}";
sb.append(v);
prev = endName + 1;
}
}
if (prev < value.length())
sb.append(value.substring(prev));
return sb.toString();
}
/**
* Reverse of Introspector.decapitalize.
* @param name The name
* @return the capitalized string
*/
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return new String(chars);
}
// -------------------- other utils --------------------
public static void clear() {
objectMethods.clear();
}
private static final Hashtable<Class<?>,Method[]> objectMethods = new Hashtable<>();
public static Method[] findMethods(Class<?> c) {
Method methods[] = objectMethods.get(c);
if (methods != null)
return methods;
methods = c.getMethods();
objectMethods.put(c, methods);
return methods;
}
@SuppressWarnings("null") // params cannot be null when comparing lengths
public static Method findMethod(Class<?> c, String name,
Class<?> params[]) {
Method methods[] = findMethods(c);
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(name)) {
Class<?> methodParams[] = methods[i].getParameterTypes();
if (params == null && methodParams.length == 0) {
return methods[i];
}
if (params.length != methodParams.length) {
continue;
}
boolean found = true;
for (int j = 0; j < params.length; j++) {
if (params[j] != methodParams[j]) {
found = false;
break;
}
}
if (found) {
return methods[i];
}
}
}
return null;
}
public static Object callMethod1(Object target, String methodN,
Object param1, String typeParam1, ClassLoader cl) throws Exception {
if (target == null || param1 == null) {
throw new IllegalArgumentException(
"IntrospectionUtils: Assert: Illegal params " +
target + " " + param1);
}
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: callMethod1 " +
target.getClass().getName() + " " +
param1.getClass().getName() + " " + typeParam1);
Class<?> params[] = new Class[1];
if (typeParam1 == null)
params[0] = param1.getClass();
else
params[0] = cl.loadClass(typeParam1);
Method m = findMethod(target.getClass(), methodN, params);
if (m == null)
throw new NoSuchMethodException(target.getClass().getName() + " "
+ methodN);
try {
return m.invoke(target, new Object[] { param1 });
} catch (InvocationTargetException ie) {
ExceptionUtils.handleThrowable(ie.getCause());
throw ie;
}
}
public static Object callMethodN(Object target, String methodN,
Object params[], Class<?> typeParams[]) throws Exception {
Method m = null;
m = findMethod(target.getClass(), methodN, typeParams);
if (m == null) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Can't find method " + methodN +
" in " + target + " CLASS " + target.getClass());
return null;
}
try {
Object o = m.invoke(target, params);
if (log.isDebugEnabled()) {
// debug
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName()).append('.')
.append(methodN).append("( ");
for (int i = 0; i < params.length; i++) {
if (i > 0)
sb.append(", ");
sb.append(params[i]);
}
sb.append(")");
log.debug("IntrospectionUtils:" + sb.toString());
}
return o;
} catch (InvocationTargetException ie) {
ExceptionUtils.handleThrowable(ie.getCause());
throw ie;
}
}
public static Object convert(String object, Class<?> paramType) {
Object result = null;
if ("java.lang.String".equals(paramType.getName())) {
result = object;
} else if ("java.lang.Integer".equals(paramType.getName())
|| "int".equals(paramType.getName())) {
try {
result = Integer.valueOf(object);
} catch (NumberFormatException ex) {
}
// Try a setFoo ( boolean )
} else if ("java.lang.Boolean".equals(paramType.getName())
|| "boolean".equals(paramType.getName())) {
result = Boolean.valueOf(object);
// Try a setFoo ( InetAddress )
} else if ("java.net.InetAddress".equals(paramType
.getName())) {
try {
result = InetAddress.getByName(object);
} catch (UnknownHostException exc) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unable to resolve host name:" +
object);
}
// Unknown type
} else {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unknown type " +
paramType.getName());
}
if (result == null) {
throw new IllegalArgumentException("Can't convert argument: " + object);
}
return result;
}
/**
* Checks to see if the specified class is an instance of or assignable from
* the specified type. The class <code>clazz</code>, all its superclasses,
* interfaces and those superinterfaces are tested for a match against
* the type name <code>type</code>.
*
* This is similar to <code>instanceof</code> or {@link Class#isAssignableFrom}
* except that the target type will not be resolved into a Class
* object, which provides some security and memory benefits.
*
* @param clazz The class to test for a match.
* @param type The name of the type that <code>clazz</code> must be.
*
* @return <code>true</code> if the <code>clazz</code> tested is an
* instance of the specified <code>type</code>,
* <code>false</code> otherwise.
*/
public static boolean isInstance(Class<?> clazz, String type) {
if (type.equals(clazz.getName())) {
return true;
}
Class<?>[] ifaces = clazz.getInterfaces();
for (Class<?> iface : ifaces) {
if (isInstance(iface, type)) {
return true;
}
}
Class<?> superClazz = clazz.getSuperclass();
if (superClazz == null) {
return false;
} else {
return isInstance(superClazz, type);
}
}
// -------------------- Get property --------------------
// This provides a layer of abstraction
public static interface PropertySource {
public String getProperty(String key);
}
public static interface SecurePropertySource extends PropertySource {
/**
* Obtain a property value, checking that code associated with the
* provided class loader has permission to access the property. If the
* {@code classLoader} is {@code null} or if {@code classLoader} does
* not implement {@link PermissionCheck} then the property value will be
* looked up <b>without</b> a call to
* {@link PermissionCheck#check(java.security.Permission)}
*
* @param key The key of the requested property
* @param classLoader The class loader associated with the code that
* trigger the property lookup
* @return The property value or {@code null} if it could not be found
* or if {@link PermissionCheck#check(java.security.Permission)}
* fails
*/
public String getProperty(String key, ClassLoader classLoader);
}
}

View File

@@ -0,0 +1,30 @@
# 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.
diagnostics.threadDumpTitle=Full thread dump
diagnostics.vmInfoClassCompilation=Class compilation
diagnostics.vmInfoClassLoading=Class loading
diagnostics.vmInfoGarbageCollectors=Garbage Collector [{0}]
diagnostics.vmInfoLogger=Logger information
diagnostics.vmInfoMemory=Memory information
diagnostics.vmInfoMemoryManagers=Memory Manager [{0}]
diagnostics.vmInfoMemoryPools=Memory Pool [{0}]
diagnostics.vmInfoOs=OS information
diagnostics.vmInfoPath=Path information
diagnostics.vmInfoRuntime=Runtime information
diagnostics.vmInfoStartup=Startup arguments
diagnostics.vmInfoSystem=System properties
diagnostics.vmInfoThreadCounts=Thread counts
diagnostics.vmInfoThreadMxBean=ThreadMXBean capabilities

View File

@@ -0,0 +1,18 @@
# 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.
diagnostics.threadDumpTitle=Kompletter Thread Dump
diagnostics.vmInfoClassCompilation=Übersetzung der Klasse
diagnostics.vmInfoLogger=Logger-Information

View File

@@ -0,0 +1,19 @@
# 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.
diagnostics.threadDumpTitle=Volcado del hilo completo
diagnostics.vmInfoClassCompilation=Compilación de clase
diagnostics.vmInfoLogger=Información de logueo
diagnostics.vmInfoSystem=Propiedades del sistema

View File

@@ -0,0 +1,30 @@
# 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.
diagnostics.threadDumpTitle=Traces complètes des threads
diagnostics.vmInfoClassCompilation=Compilation de la classe
diagnostics.vmInfoClassLoading=Chargeur de classes
diagnostics.vmInfoGarbageCollectors=Garbage Collector [{0}]
diagnostics.vmInfoLogger=Information sur le journal
diagnostics.vmInfoMemory=Information mémoire
diagnostics.vmInfoMemoryManagers=Gestionnaire de mémoire [{0}]
diagnostics.vmInfoMemoryPools=Pool de mémoire [{0}]
diagnostics.vmInfoOs=Information sur l'OS
diagnostics.vmInfoPath=Imformation de chemin
diagnostics.vmInfoRuntime=Information sur l'environnement d'exécution
diagnostics.vmInfoStartup=Arguments de démarrage
diagnostics.vmInfoSystem=Paramètres système
diagnostics.vmInfoThreadCounts=Nombre de fils d'exécution (threads)
diagnostics.vmInfoThreadMxBean=Capacités de ThreadMXBean

View File

@@ -0,0 +1,30 @@
# 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.
diagnostics.threadDumpTitle=完全なスレッドダンプ
diagnostics.vmInfoClassCompilation=Class コンパイル
diagnostics.vmInfoClassLoading=クラスローディング
diagnostics.vmInfoGarbageCollectors=ガベージコレクタ [{0}]
diagnostics.vmInfoLogger=Logger 情報
diagnostics.vmInfoMemory=メモリ情報
diagnostics.vmInfoMemoryManagers=メモリマネージャ[{0}]
diagnostics.vmInfoMemoryPools=メモリプール [{0}]
diagnostics.vmInfoOs=OS情報
diagnostics.vmInfoPath=パス情報
diagnostics.vmInfoRuntime=Runtime 情報
diagnostics.vmInfoStartup=起動引数
diagnostics.vmInfoSystem=システムプロパティ
diagnostics.vmInfoThreadCounts=スレッドカウント
diagnostics.vmInfoThreadMxBean=ThreadMXBeanの機能

View File

@@ -0,0 +1,30 @@
# 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.
diagnostics.threadDumpTitle=풀 쓰레드 덤프
diagnostics.vmInfoClassCompilation=클래스 컴파일
diagnostics.vmInfoClassLoading=클래스 로딩
diagnostics.vmInfoGarbageCollectors=Garbage Collector [{0}]
diagnostics.vmInfoLogger=Logger 정보
diagnostics.vmInfoMemory=메모리 정보
diagnostics.vmInfoMemoryManagers=메모리 매니저 [{0}]
diagnostics.vmInfoMemoryPools=메모리 풀 [{0}]
diagnostics.vmInfoOs=운영체제 정보
diagnostics.vmInfoPath=경로 정보
diagnostics.vmInfoRuntime=런타임 정보
diagnostics.vmInfoStartup=프로그램 시작 아규먼트들
diagnostics.vmInfoSystem=시스템 프로퍼티들
diagnostics.vmInfoThreadCounts=쓰레드 개수
diagnostics.vmInfoThreadMxBean=ThreadMXBean 용량정보들

View File

@@ -0,0 +1,16 @@
# 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.
diagnostics.threadDumpTitle=Сброс полной нити

View File

@@ -0,0 +1,24 @@
# 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.
diagnostics.threadDumpTitle=打印全部线程
diagnostics.vmInfoClassCompilation=:)class汇编
diagnostics.vmInfoClassLoading=类加载中
diagnostics.vmInfoGarbageCollectors=垃圾收集器[{0}]
diagnostics.vmInfoLogger=日志记录器Logger信息
diagnostics.vmInfoOs=操作系统信息
diagnostics.vmInfoRuntime=运行时信息
diagnostics.vmInfoSystem=系统.属性
diagnostics.vmInfoThreadCounts=线程数

View File

@@ -0,0 +1,99 @@
/*
* 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.tomcat.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Wraps a list of throwables as a single throwable. This is intended to be used
* when multiple actions are taken where each may throw an exception but all
* actions are taken before any errors are reported.
* <p>
* This class is <b>NOT</b> threadsafe.
*/
public class MultiThrowable extends Throwable {
private static final long serialVersionUID = 1L;
private List<Throwable> throwables = new ArrayList<>();
/**
* Add a throwable to the list of wrapped throwables.
*
* @param t The throwable to add
*/
public void add(Throwable t) {
throwables.add(t);
}
/**
* @return A read-only list of the wrapped throwables.
*/
public List<Throwable> getThrowables() {
return Collections.unmodifiableList(throwables);
}
/**
* @return {@code null} if there are no wrapped throwables, the Throwable if
* there is a single wrapped throwable or the current instance of
* there are multiple wrapped throwables
*/
public Throwable getThrowable() {
if (size() == 0) {
return null;
} else if (size() == 1) {
return throwables.get(0);
} else {
return this;
}
}
/**
* @return The number of throwables currently wrapped by this instance.
*/
public int size() {
return throwables.size();
}
/**
* Overrides the default implementation to provide a concatenation of the
* messages associated with each of the wrapped throwables. Note that the
* format of the returned String is not guaranteed to be fixed and may
* change in a future release.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(": ");
sb.append(size());
sb.append(" wrapped Throwables: ");
for (Throwable t : throwables) {
sb.append("[");
sb.append(t.getMessage());
sb.append("]");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,190 @@
/*
* 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.tomcat.util.bcel;
/**
* Constants for the project, mostly defined in the JVM specification.
*/
public final class Const {
/** One of the access flags for fields, methods, or classes.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1">
* Flag definitions for Classes in the Java Virtual Machine Specification (Java SE 9 Edition).</a>
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.5">
* Flag definitions for Fields in the Java Virtual Machine Specification (Java SE 9 Edition).</a>
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.6">
* Flag definitions for Methods in the Java Virtual Machine Specification (Java SE 9 Edition).</a>
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.6-300-D.1-D.1">
* Flag definitions for Inner Classes in the Java Virtual Machine Specification (Java SE 9 Edition).</a>
*/
public static final short ACC_FINAL = 0x0010;
/** One of the access flags for fields, methods, or classes.
*/
public static final short ACC_INTERFACE = 0x0200;
/** One of the access flags for fields, methods, or classes.
*/
public static final short ACC_ABSTRACT = 0x0400;
/** One of the access flags for fields, methods, or classes.
*/
public static final short ACC_ANNOTATION = 0x2000;
/**
* Marks a constant pool entry as type UTF-8.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.7">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Utf8 = 1;
/**
* Marks a constant pool entry as type Integer.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.4">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Integer = 3;
/**
* Marks a constant pool entry as type Float.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.4">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Float = 4;
/**
* Marks a constant pool entry as type Long.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.5">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Long = 5;
/**
* Marks a constant pool entry as type Double.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.5">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Double = 6;
/**
* Marks a constant pool entry as a Class
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.1">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Class = 7;
/**
* Marks a constant pool entry as type String
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.3">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_String = 8;
/**
* Marks a constant pool entry as a Field Reference.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.2">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Fieldref = 9;
/**
* Marks a constant pool entry as a Method Reference.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.2">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_Methodref = 10;
/**
* Marks a constant pool entry as an Interface Method Reference.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.2">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_InterfaceMethodref = 11;
/**
* Marks a constant pool entry as a name and type.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.6">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_NameAndType = 12;
/**
* Marks a constant pool entry as a Method Handle.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.8">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_MethodHandle = 15;
/**
* Marks a constant pool entry as a Method Type.
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.9">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_MethodType = 16;
/**
* Marks a constant pool entry as dynamically computed.
* @see <a href="https://bugs.openjdk.java.net/secure/attachment/74618/constant-dynamic.html">
* Change request for JEP 309</a>
*/
public static final byte CONSTANT_Dynamic = 17;
/**
* Marks a constant pool entry as an Invoke Dynamic
* @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.10">
* The Constant Pool in The Java Virtual Machine Specification</a>
*/
public static final byte CONSTANT_InvokeDynamic = 18;
/**
* Marks a constant pool entry as a Module Reference.
*
* <p>Note: Early access Java 9 support- currently subject to change</p>
*
* @see <a href="http://cr.openjdk.java.net/~mr/jigsaw/spec/lang-vm.html#jigsaw-2.6">
* JPMS: Modules in the Java Language and JVM</a>
*/
public static final byte CONSTANT_Module = 19;
/**
* Marks a constant pool entry as a Package Reference.
*
* <p>Note: Early access Java 9 support- currently subject to change</p>
*
* @see <a href="http://cr.openjdk.java.net/~mr/jigsaw/spec/lang-vm.html#jigsaw-2.6">
* JPMS: Modules in the Java Language and JVM</a>
*/
public static final byte CONSTANT_Package = 20;
/**
* The names of the types of entries in a constant pool.
* Use getConstantName instead
*/
private static final String[] CONSTANT_NAMES = {
"", "CONSTANT_Utf8", "", "CONSTANT_Integer",
"CONSTANT_Float", "CONSTANT_Long", "CONSTANT_Double",
"CONSTANT_Class", "CONSTANT_String", "CONSTANT_Fieldref",
"CONSTANT_Methodref", "CONSTANT_InterfaceMethodref",
"CONSTANT_NameAndType", "", "", "CONSTANT_MethodHandle",
"CONSTANT_MethodType", "CONSTANT_Dynamic", "CONSTANT_InvokeDynamic",
"CONSTANT_Module", "CONSTANT_Package"};
public static String getConstantName(int index) {
return CONSTANT_NAMES[index];
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.tomcat.util.bcel.classfile;
public class AnnotationElementValue extends ElementValue
{
// For annotation element values, this is the annotation
private final AnnotationEntry annotationEntry;
AnnotationElementValue(final int type, final AnnotationEntry annotationEntry,
final ConstantPool cpool)
{
super(type, cpool);
if (type != ANNOTATION) {
throw new RuntimeException(
"Only element values of type annotation can be built with this ctor - type specified: " + type);
}
this.annotationEntry = annotationEntry;
}
@Override
public String stringifyValue()
{
return annotationEntry.toString();
}
public AnnotationEntry getAnnotationEntry()
{
return annotationEntry;
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.tomcat.util.bcel.Const;
/**
* represents one annotation in the annotation table
*/
public class AnnotationEntry {
private final int type_index;
private final ConstantPool constant_pool;
private final List<ElementValuePair> element_value_pairs;
/*
* Creates an AnnotationEntry from a DataInputStream
*
* @param input
* @param constant_pool
* @throws IOException
*/
AnnotationEntry(final DataInput input, final ConstantPool constant_pool) throws IOException {
this.constant_pool = constant_pool;
type_index = input.readUnsignedShort();
final int num_element_value_pairs = input.readUnsignedShort();
element_value_pairs = new ArrayList<>(num_element_value_pairs);
for (int i = 0; i < num_element_value_pairs; i++) {
element_value_pairs.add(new ElementValuePair(input, constant_pool));
}
}
/**
* @return the annotation type name
*/
public String getAnnotationType() {
final ConstantUtf8 c = (ConstantUtf8) constant_pool.getConstant(type_index, Const.CONSTANT_Utf8);
return c.getBytes();
}
/**
* @return the element value pairs in this annotation entry
*/
public List<ElementValuePair> getElementValuePairs() {
return element_value_pairs;
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
/**
* base class for annotations
*/
public class Annotations {
private final AnnotationEntry[] annotation_table;
/**
* @param input Input stream
* @param constant_pool Array of constants
*/
Annotations(final DataInput input, final ConstantPool constant_pool) throws IOException {
final int annotation_table_length = input.readUnsignedShort();
annotation_table = new AnnotationEntry[annotation_table_length];
for (int i = 0; i < annotation_table_length; i++) {
annotation_table[i] = new AnnotationEntry(input, constant_pool);
}
}
/**
* @return the array of annotation entries in this annotation
*/
public AnnotationEntry[] getAnnotationEntries() {
return annotation_table;
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.tomcat.util.bcel.classfile;
public class ArrayElementValue extends ElementValue
{
// For array types, this is the array
private final ElementValue[] evalues;
ArrayElementValue(final int type, final ElementValue[] datums, final ConstantPool cpool)
{
super(type, cpool);
if (type != ARRAY) {
throw new RuntimeException(
"Only element values of type array can be built with this ctor - type specified: " + type);
}
this.evalues = datums;
}
@Override
public String stringifyValue()
{
final StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < evalues.length; i++)
{
sb.append(evalues[i].stringifyValue());
if ((i + 1) < evalues.length) {
sb.append(",");
}
}
sb.append("]");
return sb.toString();
}
public ElementValue[] getElementValuesArray()
{
return evalues;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.tomcat.util.bcel.classfile;
import org.apache.tomcat.util.bcel.Const;
public class ClassElementValue extends ElementValue
{
// For primitive types and string type, this points to the value entry in
// the cpool
// For 'class' this points to the class entry in the cpool
private final int idx;
ClassElementValue(final int type, final int idx, final ConstantPool cpool) {
super(type, cpool);
this.idx = idx;
}
@Override
public String stringifyValue()
{
final ConstantUtf8 cu8 = (ConstantUtf8) super.getConstantPool().getConstant(idx,
Const.CONSTANT_Utf8);
return cu8.getBytes();
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.tomcat.util.bcel.classfile;
/**
* Thrown when the BCEL attempts to read a class file and determines
* that the file is malformed or otherwise cannot be interpreted as a
* class file.
*/
public class ClassFormatException extends RuntimeException {
private static final long serialVersionUID = 3243149520175287759L;
public ClassFormatException() {
super();
}
public ClassFormatException(final String s) {
super(s);
}
}

View File

@@ -0,0 +1,247 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.BufferedInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.tomcat.util.bcel.Const;
/**
* Wrapper class that parses a given Java .class file. The method <A
* href ="#parse">parse</A> returns a <A href ="JavaClass.html">
* JavaClass</A> object on success. When an I/O error or an
* inconsistency occurs an appropriate exception is propagated back to
* the caller.
*
* The structure and the names comply, except for a few conveniences,
* exactly with the <A href="http://docs.oracle.com/javase/specs/">
* JVM specification 1.0</a>. See this paper for
* further details about the structure of a bytecode file.
*/
public final class ClassParser {
private static final int MAGIC = 0xCAFEBABE;
private final DataInput dataInputStream;
private String class_name, superclass_name;
private int access_flags; // Access rights of parsed class
private String[] interface_names; // Names of implemented interfaces
private ConstantPool constant_pool; // collection of constants
private Annotations runtimeVisibleAnnotations; // "RuntimeVisibleAnnotations" attribute defined in the class
private static final int BUFSIZE = 8192;
private static final String[] INTERFACES_EMPTY_ARRAY = new String[0];
/**
* Parses class from the given stream.
*
* @param inputStream Input stream
*/
public ClassParser(final InputStream inputStream) {
this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
}
/**
* Parses the given Java class file and return an object that represents
* the contained data, i.e., constants, methods, fields and commands.
* A <em>ClassFormatException</em> is raised, if the file is not a valid
* .class file. (This does not include verification of the byte code as it
* is performed by the java interpreter).
*
* @return Class object representing the parsed class file
* @throws IOException If an I/O occurs reading the byte code
* @throws ClassFormatException If the byte code is invalid
*/
public JavaClass parse() throws IOException, ClassFormatException {
/****************** Read headers ********************************/
// Check magic tag of class file
readID();
// Get compiler version
readVersion();
/****************** Read constant pool and related **************/
// Read constant pool entries
readConstantPool();
// Get class information
readClassInfo();
// Get interface information, i.e., implemented interfaces
readInterfaces();
/****************** Read class fields and methods ***************/
// Read class fields, i.e., the variables of the class
readFields();
// Read class methods, i.e., the functions in the class
readMethods();
// Read class attributes
readAttributes();
// Return the information we have gathered in a new object
return new JavaClass(class_name, superclass_name,
access_flags, constant_pool, interface_names,
runtimeVisibleAnnotations);
}
/**
* Reads information about the attributes of the class.
* @throws IOException
* @throws ClassFormatException
*/
private void readAttributes() throws IOException, ClassFormatException {
final int attributes_count = dataInputStream.readUnsignedShort();
for (int i = 0; i < attributes_count; i++) {
ConstantUtf8 c;
String name;
int name_index;
int length;
// Get class name from constant pool via `name_index' indirection
name_index = dataInputStream.readUnsignedShort();
c = (ConstantUtf8) constant_pool.getConstant(name_index,
Const.CONSTANT_Utf8);
name = c.getBytes();
// Length of data in bytes
length = dataInputStream.readInt();
if (name.equals("RuntimeVisibleAnnotations")) {
if (runtimeVisibleAnnotations != null) {
throw new ClassFormatException(
"RuntimeVisibleAnnotations attribute is not allowed more than once in a class file");
}
runtimeVisibleAnnotations = new Annotations(dataInputStream, constant_pool);
} else {
// All other attributes are skipped
Utility.skipFully(dataInputStream, length);
}
}
}
/**
* Reads information about the class and its super class.
* @throws IOException
* @throws ClassFormatException
*/
private void readClassInfo() throws IOException, ClassFormatException {
access_flags = dataInputStream.readUnsignedShort();
/* Interfaces are implicitly abstract, the flag should be set
* according to the JVM specification.
*/
if ((access_flags & Const.ACC_INTERFACE) != 0) {
access_flags |= Const.ACC_ABSTRACT;
}
if (((access_flags & Const.ACC_ABSTRACT) != 0)
&& ((access_flags & Const.ACC_FINAL) != 0)) {
throw new ClassFormatException("Class can't be both final and abstract");
}
int class_name_index = dataInputStream.readUnsignedShort();
class_name = Utility.getClassName(constant_pool, class_name_index);
int superclass_name_index = dataInputStream.readUnsignedShort();
if (superclass_name_index > 0) {
// May be zero -> class is java.lang.Object
superclass_name = Utility.getClassName(constant_pool, superclass_name_index);
} else {
superclass_name = "java.lang.Object";
}
}
/**
* Reads constant pool entries.
* @throws IOException
* @throws ClassFormatException
*/
private void readConstantPool() throws IOException, ClassFormatException {
constant_pool = new ConstantPool(dataInputStream);
}
/**
* Reads information about the fields of the class, i.e., its variables.
* @throws IOException
* @throws ClassFormatException
*/
private void readFields() throws IOException, ClassFormatException {
final int fields_count = dataInputStream.readUnsignedShort();
for (int i = 0; i < fields_count; i++) {
Utility.swallowFieldOrMethod(dataInputStream);
}
}
/******************** Private utility methods **********************/
/**
* Checks whether the header of the file is ok.
* Of course, this has to be the first action on successive file reads.
* @throws IOException
* @throws ClassFormatException
*/
private void readID() throws IOException, ClassFormatException {
if (dataInputStream.readInt() != MAGIC) {
throw new ClassFormatException("It is not a Java .class file");
}
}
/**
* Reads information about the interfaces implemented by this class.
* @throws IOException
* @throws ClassFormatException
*/
private void readInterfaces() throws IOException, ClassFormatException {
final int interfaces_count = dataInputStream.readUnsignedShort();
if (interfaces_count > 0) {
interface_names = new String[interfaces_count];
for (int i = 0; i < interfaces_count; i++) {
int index = dataInputStream.readUnsignedShort();
interface_names[i] = Utility.getClassName(constant_pool, index);
}
} else {
interface_names = INTERFACES_EMPTY_ARRAY;
}
}
/**
* Reads information about the methods of the class.
* @throws IOException
* @throws ClassFormatException
*/
private void readMethods() throws IOException, ClassFormatException {
final int methods_count = dataInputStream.readUnsignedShort();
for (int i = 0; i < methods_count; i++) {
Utility.swallowFieldOrMethod(dataInputStream);
}
}
/**
* Reads major and minor version of compiler which created the file.
* @throws IOException
* @throws ClassFormatException
*/
private void readVersion() throws IOException, ClassFormatException {
// file.readUnsignedShort(); // Unused minor
// file.readUnsignedShort(); // Unused major
Utility.skipFully(dataInputStream, 4);
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* Abstract superclass for classes to represent the different constant types
* in the constant pool of a class file. The classes keep closely to
* the JVM specification.
*
* @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
*/
public abstract class Constant {
/* In fact this tag is redundant since we can distinguish different
* `Constant' objects by their type, i.e., via `instanceof'. In some
* places we will use the tag for switch()es anyway.
*
* First, we want match the specification as closely as possible. Second we
* need the tag as an index to select the corresponding class name from the
* `CONSTANT_NAMES' array.
*/
protected final byte tag;
Constant(final byte tag) {
this.tag = tag;
}
/**
* @return Tag of constant, i.e., its type. No setTag() method to avoid
* confusion.
*/
public final byte getTag() {
return tag;
}
/**
* Read one constant from the given input, the type depends on a tag byte.
*
* @param dataInput Input stream
* @return Constant object
* @throws IOException if an I/O error occurs reading from the given {@code dataInput}.
* @throws ClassFormatException if the next byte is not recognized
*/
static Constant readConstant(final DataInput dataInput) throws IOException, ClassFormatException {
final byte b = dataInput.readByte(); // Read tag byte
int skipSize;
switch (b) {
case Const.CONSTANT_Class:
return new ConstantClass(dataInput);
case Const.CONSTANT_Integer:
return new ConstantInteger(dataInput);
case Const.CONSTANT_Float:
return new ConstantFloat(dataInput);
case Const.CONSTANT_Long:
return new ConstantLong(dataInput);
case Const.CONSTANT_Double:
return new ConstantDouble(dataInput);
case Const.CONSTANT_Utf8:
return ConstantUtf8.getInstance(dataInput);
case Const.CONSTANT_String:
case Const.CONSTANT_MethodType:
case Const.CONSTANT_Module:
case Const.CONSTANT_Package:
skipSize = 2; // unsigned short
break;
case Const.CONSTANT_MethodHandle:
skipSize = 3; // unsigned byte, unsigned short
break;
case Const.CONSTANT_Fieldref:
case Const.CONSTANT_Methodref:
case Const.CONSTANT_InterfaceMethodref:
case Const.CONSTANT_NameAndType:
case Const.CONSTANT_Dynamic:
case Const.CONSTANT_InvokeDynamic:
skipSize = 4; // unsigned short, unsigned short
break;
default:
throw new ClassFormatException("Invalid byte tag in constant pool: " + b);
}
Utility.skipFully(dataInput, skipSize);
return null;
}
@Override
public String toString() {
return "[" + tag + "]";
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* This class is derived from the abstract {@link Constant}
* and represents a reference to a (external) class.
*
* @see Constant
*/
public final class ConstantClass extends Constant {
private final int name_index; // Identical to ConstantString except for the name
/**
* Constructs an instance from file data.
*
* @param dataInput Input stream
* @throws IOException if an I/O error occurs reading from the given {@code dataInput}.
*/
ConstantClass(final DataInput dataInput) throws IOException {
super(Const.CONSTANT_Class);
this.name_index = dataInput.readUnsignedShort();
}
/**
* @return Name index in constant pool of class name.
*/
public int getNameIndex() {
return name_index;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* This class is derived from the abstract {@link Constant}
* and represents a reference to a Double object.
*
* @see Constant
*/
public final class ConstantDouble extends Constant {
private final double bytes;
/**
* Initialize instance from file data.
*
* @param file Input stream
* @throws IOException
*/
ConstantDouble(final DataInput file) throws IOException {
super(Const.CONSTANT_Double);
this.bytes = file.readDouble();
}
/**
* @return data, i.e., 8 bytes.
*/
public double getBytes() {
return bytes;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* This class is derived from the abstract {@link Constant}
* and represents a reference to a float object.
*
* @see Constant
*/
public final class ConstantFloat extends Constant {
private final float bytes;
/**
* Initialize instance from file data.
*
* @param file Input stream
* @throws IOException
*/
ConstantFloat(final DataInput file) throws IOException {
super(Const.CONSTANT_Float);
this.bytes = file.readFloat();
}
/**
* @return data, i.e., 4 bytes.
*/
public float getBytes() {
return bytes;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* This class is derived from the abstract {@link Constant}
* and represents a reference to an int object.
*
* @see Constant
*/
public final class ConstantInteger extends Constant {
private final int bytes;
/**
* Initialize instance from file data.
*
* @param file Input stream
* @throws IOException
*/
ConstantInteger(final DataInput file) throws IOException {
super(Const.CONSTANT_Integer);
this.bytes = file.readInt();
}
/**
* @return data, i.e., 4 bytes.
*/
public int getBytes() {
return bytes;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* This class is derived from the abstract {@link Constant}
* and represents a reference to a long object.
*
* @see Constant
*/
public final class ConstantLong extends Constant {
private final long bytes;
/**
* Initialize instance from file data.
*
* @param input Input stream
* @throws IOException
*/
ConstantLong(final DataInput input) throws IOException {
super(Const.CONSTANT_Long);
this.bytes = input.readLong();
}
/**
* @return data, i.e., 8 bytes.
*/
public long getBytes() {
return bytes;
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* This class represents the constant pool, i.e., a table of constants, of
* a parsed classfile. It may contain null references, due to the JVM
* specification that skips an entry after an 8-byte constant (double,
* long) entry. Those interested in generating constant pools
* programatically should see <a href="../generic/ConstantPoolGen.html">
* ConstantPoolGen</a>.
* @see Constant
*/
public class ConstantPool {
private final Constant[] constant_pool;
/**
* Reads constants from given input stream.
*
* @param input Input stream
* @throws IOException
* @throws ClassFormatException
*/
ConstantPool(final DataInput input) throws IOException, ClassFormatException {
final int constant_pool_count = input.readUnsignedShort();
constant_pool = new Constant[constant_pool_count];
/* constant_pool[0] is unused by the compiler and may be used freely
* by the implementation.
*/
for (int i = 1; i < constant_pool_count; i++) {
constant_pool[i] = Constant.readConstant(input);
/* Quote from the JVM specification:
* "All eight byte constants take up two spots in the constant pool.
* If this is the n'th byte in the constant pool, then the next item
* will be numbered n+2"
*
* Thus we have to increment the index counter.
*/
if (constant_pool[i] != null) {
byte tag = constant_pool[i].getTag();
if ((tag == Const.CONSTANT_Double) || (tag == Const.CONSTANT_Long)) {
i++;
}
}
}
}
/**
* Gets constant from constant pool.
*
* @param index Index in constant pool
* @return Constant value
* @see Constant
*/
public Constant getConstant( final int index ) {
if (index >= constant_pool.length || index < 0) {
throw new ClassFormatException("Invalid constant pool reference: " + index
+ ". Constant pool size is: " + constant_pool.length);
}
return constant_pool[index];
}
/**
* Gets constant from constant pool and check whether it has the
* expected type.
*
* @param index Index in constant pool
* @param tag Tag of expected constant, i.e., its type
* @return Constant value
* @see Constant
* @throws ClassFormatException If the constant is not of the expected type
*/
public Constant getConstant( final int index, final byte tag ) throws ClassFormatException {
Constant c;
c = getConstant(index);
if (c == null) {
throw new ClassFormatException("Constant pool at index " + index + " is null.");
}
if (c.getTag() != tag) {
throw new ClassFormatException("Expected class `" + Const.getConstantName(tag)
+ "' at index " + index + " and got " + c);
}
return c;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* This class is derived from the abstract
* <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class
* and represents a reference to a Utf8 encoded string.
*
* @see Constant
*/
public final class ConstantUtf8 extends Constant {
private final String bytes;
static ConstantUtf8 getInstance(final DataInput input) throws IOException {
return new ConstantUtf8(input.readUTF());
}
/**
* @param bytes Data
*/
private ConstantUtf8(final String bytes) {
super(Const.CONSTANT_Utf8);
if (bytes == null) {
throw new IllegalArgumentException("bytes must not be null!");
}
this.bytes = bytes;
}
/**
* @return Data converted to string.
*/
public final String getBytes() {
return bytes;
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
public abstract class ElementValue
{
private final int type;
private final ConstantPool cpool;
ElementValue(final int type, final ConstantPool cpool) {
this.type = type;
this.cpool = cpool;
}
public abstract String stringifyValue();
public static final byte STRING = 's';
public static final byte ENUM_CONSTANT = 'e';
public static final byte CLASS = 'c';
public static final byte ANNOTATION = '@';
public static final byte ARRAY = '[';
public static final byte PRIMITIVE_INT = 'I';
public static final byte PRIMITIVE_BYTE = 'B';
public static final byte PRIMITIVE_CHAR = 'C';
public static final byte PRIMITIVE_DOUBLE = 'D';
public static final byte PRIMITIVE_FLOAT = 'F';
public static final byte PRIMITIVE_LONG = 'J';
public static final byte PRIMITIVE_SHORT = 'S';
public static final byte PRIMITIVE_BOOLEAN = 'Z';
public static ElementValue readElementValue(final DataInput input, final ConstantPool cpool) throws IOException
{
final byte type = input.readByte();
switch (type)
{
case PRIMITIVE_BYTE:
case PRIMITIVE_CHAR:
case PRIMITIVE_DOUBLE:
case PRIMITIVE_FLOAT:
case PRIMITIVE_INT:
case PRIMITIVE_LONG:
case PRIMITIVE_SHORT:
case PRIMITIVE_BOOLEAN:
case STRING:
return new SimpleElementValue(type, input.readUnsignedShort(), cpool);
case ENUM_CONSTANT:
input.readUnsignedShort(); // Unused type_index
return new EnumElementValue(ENUM_CONSTANT, input.readUnsignedShort(), cpool);
case CLASS:
return new ClassElementValue(CLASS, input.readUnsignedShort(), cpool);
case ANNOTATION:
// TODO isRuntimeVisible
return new AnnotationElementValue(ANNOTATION, new AnnotationEntry(input, cpool), cpool);
case ARRAY:
final int numArrayVals = input.readUnsignedShort();
final ElementValue[] evalues = new ElementValue[numArrayVals];
for (int j = 0; j < numArrayVals; j++)
{
evalues[j] = ElementValue.readElementValue(input, cpool);
}
return new ArrayElementValue(ARRAY, evalues, cpool);
default:
throw new ClassFormatException(
"Unexpected element value kind in annotation: " + type);
}
}
final ConstantPool getConstantPool() {
return cpool;
}
final int getType() {
return type;
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* an annotation's element value pair
*
* @since 6.0
*/
public class ElementValuePair
{
private final ElementValue elementValue;
private final ConstantPool constantPool;
private final int elementNameIndex;
ElementValuePair(final DataInput file, final ConstantPool constantPool) throws IOException {
this.constantPool = constantPool;
this.elementNameIndex = file.readUnsignedShort();
this.elementValue = ElementValue.readElementValue(file, constantPool);
}
public String getNameString()
{
final ConstantUtf8 c = (ConstantUtf8) constantPool.getConstant(
elementNameIndex, Const.CONSTANT_Utf8);
return c.getBytes();
}
public final ElementValue getValue()
{
return elementValue;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.tomcat.util.bcel.classfile;
import org.apache.tomcat.util.bcel.Const;
public class EnumElementValue extends ElementValue
{
private final int valueIdx;
EnumElementValue(final int type, final int valueIdx, final ConstantPool cpool) {
super(type, cpool);
if (type != ENUM_CONSTANT)
throw new RuntimeException(
"Only element values of type enum can be built with this ctor - type specified: " + type);
this.valueIdx = valueIdx;
}
@Override
public String stringifyValue()
{
final ConstantUtf8 cu8 = (ConstantUtf8) super.getConstantPool().getConstant(valueIdx,
Const.CONSTANT_Utf8);
return cu8.getBytes();
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.tomcat.util.bcel.classfile;
/**
* Represents a Java class, i.e., the data structures, constant pool,
* fields, methods and commands contained in a Java .class file.
* See <a href="http://docs.oracle.com/javase/specs/">JVM specification</a> for details.
* The intent of this class is to represent a parsed or otherwise existing
* class file. Those interested in programatically generating classes
* should see the <a href="../generic/ClassGen.html">ClassGen</a> class.
*/
public class JavaClass {
private final int access_flags;
private final String class_name;
private final String superclass_name;
private final String[] interface_names;
private final Annotations runtimeVisibleAnnotations; // "RuntimeVisibleAnnotations" attribute defined in the class
/**
* Constructor gets all contents as arguments.
*
* @param class_name Name of this class.
* @param superclass_name Name of this class's superclass.
* @param access_flags Access rights defined by bit flags
* @param constant_pool Array of constants
* @param interface_names Implemented interfaces
* @param runtimeVisibleAnnotations "RuntimeVisibleAnnotations" attribute defined on the Class, or null
*/
JavaClass(final String class_name, final String superclass_name,
final int access_flags, final ConstantPool constant_pool, final String[] interface_names,
final Annotations runtimeVisibleAnnotations) {
this.access_flags = access_flags;
this.runtimeVisibleAnnotations = runtimeVisibleAnnotations;
this.class_name = class_name;
this.superclass_name = superclass_name;
this.interface_names = interface_names;
}
/**
* @return Access flags of the object aka. "modifiers".
*/
public final int getAccessFlags() {
return access_flags;
}
/**
* Return annotations entries from "RuntimeVisibleAnnotations" attribute on
* the class, if there is any.
*
* @return An array of entries or {@code null}
*/
public AnnotationEntry[] getAnnotationEntries() {
if (runtimeVisibleAnnotations != null) {
return runtimeVisibleAnnotations.getAnnotationEntries();
}
return null;
}
/**
* @return Class name.
*/
public String getClassName() {
return class_name;
}
/**
* @return Names of implemented interfaces.
*/
public String[] getInterfaceNames() {
return interface_names;
}
/**
* returns the super class name of this class. In the case that this class is
* java.lang.Object, it will return itself (java.lang.Object). This is probably incorrect
* but isn't fixed at this time to not break existing clients.
*
* @return Superclass name.
*/
public String getSuperclassName() {
return superclass_name;
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.tomcat.util.bcel.classfile;
import org.apache.tomcat.util.bcel.Const;
public class SimpleElementValue extends ElementValue
{
private final int index;
SimpleElementValue(final int type, final int index, final ConstantPool cpool) {
super(type, cpool);
this.index = index;
}
/**
* @return Value entry index in the cpool
*/
public int getIndex()
{
return index;
}
// Whatever kind of value it is, return it as a string
@Override
public String stringifyValue()
{
final ConstantPool cpool = super.getConstantPool();
final int _type = super.getType();
switch (_type)
{
case PRIMITIVE_INT:
final ConstantInteger c = (ConstantInteger) cpool.getConstant(getIndex(),
Const.CONSTANT_Integer);
return Integer.toString(c.getBytes());
case PRIMITIVE_LONG:
final ConstantLong j = (ConstantLong) cpool.getConstant(getIndex(),
Const.CONSTANT_Long);
return Long.toString(j.getBytes());
case PRIMITIVE_DOUBLE:
final ConstantDouble d = (ConstantDouble) cpool.getConstant(getIndex(),
Const.CONSTANT_Double);
return Double.toString(d.getBytes());
case PRIMITIVE_FLOAT:
final ConstantFloat f = (ConstantFloat) cpool.getConstant(getIndex(),
Const.CONSTANT_Float);
return Float.toString(f.getBytes());
case PRIMITIVE_SHORT:
final ConstantInteger s = (ConstantInteger) cpool.getConstant(getIndex(),
Const.CONSTANT_Integer);
return Integer.toString(s.getBytes());
case PRIMITIVE_BYTE:
final ConstantInteger b = (ConstantInteger) cpool.getConstant(getIndex(),
Const.CONSTANT_Integer);
return Integer.toString(b.getBytes());
case PRIMITIVE_CHAR:
final ConstantInteger ch = (ConstantInteger) cpool.getConstant(
getIndex(), Const.CONSTANT_Integer);
return String.valueOf((char)ch.getBytes());
case PRIMITIVE_BOOLEAN:
final ConstantInteger bo = (ConstantInteger) cpool.getConstant(
getIndex(), Const.CONSTANT_Integer);
if (bo.getBytes() == 0) {
return "false";
}
return "true";
case STRING:
final ConstantUtf8 cu8 = (ConstantUtf8) cpool.getConstant(getIndex(),
Const.CONSTANT_Utf8);
return cu8.getBytes();
default:
throw new RuntimeException("SimpleElementValue class does not know how to stringify type " + _type);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.tomcat.util.bcel.classfile;
import java.io.DataInput;
import java.io.EOFException;
import java.io.IOException;
import org.apache.tomcat.util.bcel.Const;
/**
* Utility functions that do not really belong to any class in particular.
*/
final class Utility {
private Utility() {
// Hide default constructor
}
/**
* Shorten long class name <em>str</em>, i.e., chop off the <em>prefix</em>,
* if the
* class name starts with this string and the flag <em>chopit</em> is true.
* Slashes <em>/</em> are converted to dots <em>.</em>.
*
* @param str The long class name
* @return Compacted class name
*/
static String compactClassName(final String str) {
return str.replace('/', '.'); // Is `/' on all systems, even DOS
}
static String getClassName(final ConstantPool constant_pool, final int index) {
Constant c = constant_pool.getConstant(index, Const.CONSTANT_Class);
int i = ((ConstantClass) c).getNameIndex();
// Finally get the string from the constant pool
c = constant_pool.getConstant(i, Const.CONSTANT_Utf8);
String name = ((ConstantUtf8) c).getBytes();
return compactClassName(name);
}
static void skipFully(final DataInput file, final int length) throws IOException {
int total = file.skipBytes(length);
if (total != length) {
throw new EOFException();
}
}
static void swallowFieldOrMethod(final DataInput file)
throws IOException {
// file.readUnsignedShort(); // Unused access flags
// file.readUnsignedShort(); // name index
// file.readUnsignedShort(); // signature index
skipFully(file, 6);
int attributes_count = file.readUnsignedShort();
for (int i = 0; i < attributes_count; i++) {
swallowAttribute(file);
}
}
static void swallowAttribute(final DataInput file)
throws IOException {
//file.readUnsignedShort(); // Unused name index
skipFully(file, 2);
// Length of data in bytes
int length = file.readInt();
skipFully(file, length);
}
}

View File

@@ -0,0 +1,27 @@
<!--
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.
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
</head>
<body bgcolor="white">
<p>
This package contains the classes that describe the structure of a
Java class file and a class file parser.
</p>
</body>
</html>

View File

@@ -0,0 +1,30 @@
<!--
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.
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
</head>
<body bgcolor="white">
<p>
This package contains basic classes for the
<a href="https://commons.apache.org/bcel/">Byte Code Engineering Library</a>
and constants defined by the
<a href="http://docs.oracle.com/javase/specs/">
JVM specification</a>.
</p>
</body>
</html>

View File

@@ -0,0 +1,181 @@
/*
* 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.tomcat.util.buf;
import java.io.Serializable;
/**
* Base class for the *Chunk implementation to reduce duplication.
*/
public abstract class AbstractChunk implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
/*
* JVMs may limit the maximum array size to slightly less than
* Integer.MAX_VALUE. On markt's desktop the limit is MAX_VALUE - 2.
* Comments in the JRE source code for ArrayList and other classes indicate
* that it may be as low as MAX_VALUE - 8 on some systems.
*/
public static final int ARRAY_MAX_SIZE = Integer.MAX_VALUE - 8;
private int hashCode = 0;
protected boolean hasHashCode = false;
protected boolean isSet;
private int limit = -1;
protected int start;
protected int end;
/**
* Maximum amount of data in this buffer. If -1 or not set, the buffer will
* grow to {{@link #ARRAY_MAX_SIZE}. Can be smaller than the current buffer
* size ( which will not shrink ). When the limit is reached, the buffer
* will be flushed (if out is set) or throw exception.
*
* @param limit The new limit
*/
public void setLimit(int limit) {
this.limit = limit;
}
public int getLimit() {
return limit;
}
protected int getLimitInternal() {
if (limit > 0) {
return limit;
} else {
return ARRAY_MAX_SIZE;
}
}
/**
* @return the start position of the data in the buffer
*/
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
public void setEnd(int i) {
end = i;
}
// TODO: Deprecate offset and use start
public int getOffset() {
return start;
}
public void setOffset(int off) {
if (end < off) {
end = off;
}
start = off;
}
/**
* @return the length of the data in the buffer
*/
public int getLength() {
return end - start;
}
public boolean isNull() {
if (end > 0) {
return false;
}
return !isSet;
}
public int indexOf(String src, int srcOff, int srcLen, int myOff) {
char first = src.charAt(srcOff);
// Look for first char
int srcEnd = srcOff + srcLen;
mainLoop: for (int i = myOff + start; i <= (end - srcLen); i++) {
if (getBufferElement(i) != first) {
continue;
}
// found first char, now look for a match
int myPos = i + 1;
for (int srcPos = srcOff + 1; srcPos < srcEnd;) {
if (getBufferElement(myPos++) != src.charAt(srcPos++)) {
continue mainLoop;
}
}
return i - start; // found it
}
return -1;
}
/**
* Resets the chunk to an uninitialized state.
*/
public void recycle() {
hasHashCode = false;
isSet = false;
start = 0;
end = 0;
}
@Override
public int hashCode() {
if (hasHashCode) {
return hashCode;
}
int code = 0;
code = hash();
hashCode = code;
hasHashCode = true;
return code;
}
public int hash() {
int code = 0;
for (int i = start; i < end; i++) {
code = code * 37 + getBufferElement(i);
}
return code;
}
protected abstract int getBufferElement(int index);
}

View File

@@ -0,0 +1,103 @@
/*
* 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.tomcat.util.buf;
/**
* This class implements some basic ASCII character handling functions.
*
* @author dac@eng.sun.com
* @author James Todd [gonzo@eng.sun.com]
*/
public final class Ascii {
/*
* Character translation tables.
*/
private static final byte[] toLower = new byte[256];
/*
* Character type tables.
*/
private static final boolean[] isDigit = new boolean[256];
private static final long OVERFLOW_LIMIT = Long.MAX_VALUE / 10;
/*
* Initialize character translation and type tables.
*/
static {
for (int i = 0; i < 256; i++) {
toLower[i] = (byte)i;
}
for (int lc = 'a'; lc <= 'z'; lc++) {
int uc = lc + 'A' - 'a';
toLower[uc] = (byte)lc;
}
for (int d = '0'; d <= '9'; d++) {
isDigit[d] = true;
}
}
/**
* Returns the lower case equivalent of the specified ASCII character.
* @param c The char
* @return the lower case equivalent char
*/
public static int toLower(int c) {
return toLower[c & 0xff] & 0xff;
}
/**
* @return <code>true</code> if the specified ASCII character is a digit.
* @param c The char
*/
private static boolean isDigit(int c) {
return isDigit[c & 0xff];
}
/**
* Parses an unsigned long from the specified subarray of bytes.
* @param b the bytes to parse
* @param off the start offset of the bytes
* @param len the length of the bytes
* @return the long value
* @exception NumberFormatException if the long format was invalid
*/
public static long parseLong(byte[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
long n = c - '0';
while (--len > 0) {
if (isDigit(c = b[off++]) &&
(n < OVERFLOW_LIMIT || (n == OVERFLOW_LIMIT && (c - '0') < 8))) {
n = n * 10 + c - '0';
} else {
throw new NumberFormatException();
}
}
return n;
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.tomcat.util.buf;
import java.math.BigInteger;
import org.apache.tomcat.util.res.StringManager;
/**
* This is a very basic ASN.1 parser that provides the limited functionality
* required by Tomcat. It is a long way from a complete parser.
*
* TODO: Consider extending this parser and refactoring the SpnegoTokenFixer to
* use it.
*/
public class Asn1Parser {
private static final StringManager sm = StringManager.getManager(Asn1Parser.class);
private final byte[] source;
private int pos = 0;
public Asn1Parser(byte[] source) {
this.source = source;
}
public void parseTag(int tag) {
int value = next();
if (value != tag) {
throw new IllegalArgumentException(sm.getString("asn1Parser.tagMismatch",
Integer.valueOf(tag), Integer.valueOf(value)));
}
}
public void parseFullLength() {
int len = parseLength();
if (len + pos != source.length) {
throw new IllegalArgumentException(sm.getString("asn1Parser.lengthInvalid",
Integer.valueOf(len), Integer.valueOf(source.length - pos)));
}
}
public int parseLength() {
int len = next();
if (len > 127) {
int bytes = len - 128;
len = 0;
for (int i = 0; i < bytes; i++) {
len = len << 8;
len = len + next();
}
}
return len;
}
public BigInteger parseInt() {
parseTag(0x02);
int len = parseLength();
byte[] val = new byte[len];
System.arraycopy(source, pos, val, 0, len);
pos += len;
return new BigInteger(val);
}
public void parseBytes(byte[] dest) {
System.arraycopy(source, pos, dest, 0, dest.length);
pos += dest.length;
}
private int next() {
return source[pos++] & 0xFF;
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.tomcat.util.buf;
public class Asn1Writer {
public static byte[] writeSequence(byte[]... components) {
int len = 0;
for (byte[] component : components) {
len += component.length;
}
byte[] combined = new byte[len];
int pos = 0;
for (byte[] component : components) {
System.arraycopy(component, 0, combined, pos, component.length);
pos += component.length;
}
return writeTag((byte) 0x30, combined);
}
public static byte[] writeInteger(int value) {
// How many bytes required to write the value? No more than 4 for int.
int valueSize = 1;
while ((value >> (valueSize * 8)) > 0) {
valueSize++;
}
byte[] valueBytes = new byte[valueSize];
int i = 0;
while (valueSize > 0) {
valueBytes[i] = (byte) (value >> (8 * (valueSize - 1)));
value = value >> 8;
valueSize--;
i++;
}
return writeTag((byte) 0x02, valueBytes);
}
public static byte[] writeOctetString(byte[] data) {
return writeTag((byte) 0x04, data);
}
public static byte[] writeTag(byte tagId, byte[] data) {
int dataSize = data.length;
// How many bytes to write the length?
int lengthSize = 1;
if (dataSize >127) {
// 1 byte we have is now used to record how many bytes we need to
// record a length > 127
// Result is lengthSize = 1 + number of bytes to record length
do {
lengthSize++;
}
while ((dataSize >> (lengthSize * 8)) > 0);
}
// 1 for tag + lengthSize + dataSize
byte[] result = new byte[1 + lengthSize + dataSize];
result[0] = tagId;
if (dataSize < 128) {
result[1] = (byte) dataSize;
} else {
// lengthSize is 1 + number of bytes for length
result[1] = (byte) (127 + lengthSize);
int i = lengthSize;
while (dataSize > 0) {
result[i] = (byte) (dataSize & 0xFF);
dataSize = dataSize >> 8;
i--;
}
}
System.arraycopy(data, 0, result, 1 + lengthSize, data.length);
return result;
}
}

View File

@@ -0,0 +1,284 @@
/*
* 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.tomcat.util.buf;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import org.apache.tomcat.util.res.StringManager;
/**
* NIO based character decoder.
*/
public class B2CConverter {
private static final StringManager sm =
StringManager.getManager(Constants.Package);
private static final CharsetCache charsetCache = new CharsetCache();
// Protected so unit tests can use it
protected static final int LEFTOVER_SIZE = 9;
/**
* Obtain the Charset for the given encoding
*
* @param enc The name of the encoding for the required charset
*
* @return The Charset corresponding to the requested encoding
*
* @throws UnsupportedEncodingException If the requested Charset is not
* available
*/
public static Charset getCharset(String enc)
throws UnsupportedEncodingException {
// Encoding names should all be ASCII
String lowerCaseEnc = enc.toLowerCase(Locale.ENGLISH);
return getCharsetLower(lowerCaseEnc);
}
/**
* Only to be used when it is known that the encoding name is in lower case.
* @param lowerCaseEnc The name of the encoding for the required charset in
* lower case
*
* @return The Charset corresponding to the requested encoding
*
* @throws UnsupportedEncodingException If the requested Charset is not
* available
*
* @deprecated Will be removed in Tomcat 9.0.x
*/
@Deprecated
public static Charset getCharsetLower(String lowerCaseEnc)
throws UnsupportedEncodingException {
Charset charset = charsetCache.getCharset(lowerCaseEnc);
if (charset == null) {
// Pre-population of the cache means this must be invalid
throw new UnsupportedEncodingException(
sm.getString("b2cConverter.unknownEncoding", lowerCaseEnc));
}
return charset;
}
private final CharsetDecoder decoder;
private ByteBuffer bb = null;
private CharBuffer cb = null;
/**
* Leftover buffer used for incomplete characters.
*/
private final ByteBuffer leftovers;
public B2CConverter(Charset charset) {
this(charset, false);
}
public B2CConverter(Charset charset, boolean replaceOnError) {
byte[] left = new byte[LEFTOVER_SIZE];
leftovers = ByteBuffer.wrap(left);
CodingErrorAction action;
if (replaceOnError) {
action = CodingErrorAction.REPLACE;
} else {
action = CodingErrorAction.REPORT;
}
// Special case. Use the Apache Harmony based UTF-8 decoder because it
// - a) rejects invalid sequences that the JVM decoder does not
// - b) fails faster for some invalid sequences
if (charset.equals(StandardCharsets.UTF_8)) {
decoder = new Utf8Decoder();
} else {
decoder = charset.newDecoder();
}
decoder.onMalformedInput(action);
decoder.onUnmappableCharacter(action);
}
/**
* Reset the decoder state.
*/
public void recycle() {
decoder.reset();
leftovers.position(0);
}
/**
* Convert the given bytes to characters.
*
* @param bc byte input
* @param cc char output
* @param endOfInput Is this all of the available data
*
* @throws IOException If the conversion can not be completed
*/
public void convert(ByteChunk bc, CharChunk cc, boolean endOfInput)
throws IOException {
if ((bb == null) || (bb.array() != bc.getBuffer())) {
// Create a new byte buffer if anything changed
bb = ByteBuffer.wrap(bc.getBuffer(), bc.getStart(), bc.getLength());
} else {
// Initialize the byte buffer
bb.limit(bc.getEnd());
bb.position(bc.getStart());
}
if ((cb == null) || (cb.array() != cc.getBuffer())) {
// Create a new char buffer if anything changed
cb = CharBuffer.wrap(cc.getBuffer(), cc.getEnd(),
cc.getBuffer().length - cc.getEnd());
} else {
// Initialize the char buffer
cb.limit(cc.getBuffer().length);
cb.position(cc.getEnd());
}
CoderResult result = null;
// Parse leftover if any are present
if (leftovers.position() > 0) {
int pos = cb.position();
// Loop until one char is decoded or there is a decoder error
do {
leftovers.put(bc.substractB());
leftovers.flip();
result = decoder.decode(leftovers, cb, endOfInput);
leftovers.position(leftovers.limit());
leftovers.limit(leftovers.array().length);
} while (result.isUnderflow() && (cb.position() == pos));
if (result.isError() || result.isMalformed()) {
result.throwException();
}
bb.position(bc.getStart());
leftovers.position(0);
}
// Do the decoding and get the results into the byte chunk and the char
// chunk
result = decoder.decode(bb, cb, endOfInput);
if (result.isError() || result.isMalformed()) {
result.throwException();
} else if (result.isOverflow()) {
// Propagate current positions to the byte chunk and char chunk, if
// this continues the char buffer will get resized
bc.setOffset(bb.position());
cc.setEnd(cb.position());
} else if (result.isUnderflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.setOffset(bb.position());
cc.setEnd(cb.position());
// Put leftovers in the leftovers byte buffer
if (bc.getLength() > 0) {
leftovers.limit(leftovers.array().length);
leftovers.position(bc.getLength());
bc.substract(leftovers.array(), 0, bc.getLength());
}
}
}
/**
* Convert the given bytes to characters.
*
* @param bc byte input
* @param cc char output
* @param ic byte input channel
* @param endOfInput Is this all of the available data
*
* @throws IOException If the conversion can not be completed
*/
public void convert(ByteBuffer bc, CharBuffer cc, ByteChunk.ByteInputChannel ic, boolean endOfInput)
throws IOException {
if ((bb == null) || (bb.array() != bc.array())) {
// Create a new byte buffer if anything changed
bb = ByteBuffer.wrap(bc.array(), bc.arrayOffset() + bc.position(), bc.remaining());
} else {
// Initialize the byte buffer
bb.limit(bc.limit());
bb.position(bc.position());
}
if ((cb == null) || (cb.array() != cc.array())) {
// Create a new char buffer if anything changed
cb = CharBuffer.wrap(cc.array(), cc.limit(), cc.capacity() - cc.limit());
} else {
// Initialize the char buffer
cb.limit(cc.capacity());
cb.position(cc.limit());
}
CoderResult result = null;
// Parse leftover if any are present
if (leftovers.position() > 0) {
int pos = cb.position();
// Loop until one char is decoded or there is a decoder error
do {
byte chr;
if (bc.remaining() == 0) {
int n = ic.realReadBytes();
chr = n < 0 ? -1 : bc.get();
} else {
chr = bc.get();
}
leftovers.put(chr);
leftovers.flip();
result = decoder.decode(leftovers, cb, endOfInput);
leftovers.position(leftovers.limit());
leftovers.limit(leftovers.array().length);
} while (result.isUnderflow() && (cb.position() == pos));
if (result.isError() || result.isMalformed()) {
result.throwException();
}
bb.position(bc.position());
leftovers.position(0);
}
// Do the decoding and get the results into the byte chunk and the char
// chunk
result = decoder.decode(bb, cb, endOfInput);
if (result.isError() || result.isMalformed()) {
result.throwException();
} else if (result.isOverflow()) {
// Propagate current positions to the byte chunk and char chunk, if
// this continues the char buffer will get resized
bc.position(bb.position());
cc.limit(cb.position());
} else if (result.isUnderflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.position(bb.position());
cc.limit(cb.position());
// Put leftovers in the leftovers byte buffer
if (bc.remaining() > 0) {
leftovers.limit(leftovers.array().length);
leftovers.position(bc.remaining());
bc.get(leftovers.array(), 0, bc.remaining());
}
}
}
public Charset getCharset() {
return decoder.charset();
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.tomcat.util.buf;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Simple wrapper for a {@link ByteBuffer} that remembers if the buffer has been
* flipped or not.
*/
public class ByteBufferHolder {
private final ByteBuffer buf;
private final AtomicBoolean flipped;
public ByteBufferHolder(ByteBuffer buf, boolean flipped) {
this.buf = buf;
this.flipped = new AtomicBoolean(flipped);
}
public ByteBuffer getBuf() {
return buf;
}
public boolean isFlipped() {
return flipped.get();
}
public boolean flip() {
if (flipped.compareAndSet(false, true)) {
buf.flip();
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* 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.tomcat.util.buf;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.compat.JreCompat;
import org.apache.tomcat.util.res.StringManager;
public class ByteBufferUtils {
private static final StringManager sm =
StringManager.getManager(Constants.Package);
private static final Log log = LogFactory.getLog(ByteBufferUtils.class);
private static final Object unsafe;
private static final Method cleanerMethod;
private static final Method cleanMethod;
private static final Method invokeCleanerMethod;
static {
ByteBuffer tempBuffer = ByteBuffer.allocateDirect(0);
Method cleanerMethodLocal = null;
Method cleanMethodLocal = null;
Object unsafeLocal = null;
Method invokeCleanerMethodLocal = null;
if (JreCompat.isJre9Available()) {
try {
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field theUnsafe = clazz.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafeLocal = theUnsafe.get(null);
invokeCleanerMethodLocal = clazz.getMethod("invokeCleaner", ByteBuffer.class);
invokeCleanerMethodLocal.invoke(unsafeLocal, tempBuffer);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException
| ClassNotFoundException | NoSuchFieldException e) {
log.warn(sm.getString("byteBufferUtils.cleaner"), e);
unsafeLocal = null;
invokeCleanerMethodLocal = null;
}
} else {
try {
cleanerMethodLocal = tempBuffer.getClass().getMethod("cleaner");
cleanerMethodLocal.setAccessible(true);
Object cleanerObject = cleanerMethodLocal.invoke(tempBuffer);
cleanMethodLocal = cleanerObject.getClass().getMethod("clean");
cleanMethodLocal.invoke(cleanerObject);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException e) {
log.warn(sm.getString("byteBufferUtils.cleaner"), e);
cleanerMethodLocal = null;
cleanMethodLocal = null;
}
}
cleanerMethod = cleanerMethodLocal;
cleanMethod = cleanMethodLocal;
unsafe = unsafeLocal;
invokeCleanerMethod = invokeCleanerMethodLocal;
}
private ByteBufferUtils() {
// Hide the default constructor since this is a utility class.
}
/**
* Expands buffer to the given size unless it is already as big or bigger.
* Buffers are assumed to be in 'write to' mode since there would be no need
* to expand a buffer while it was in 'read from' mode.
*
* @param in Buffer to expand
* @param newSize The size t which the buffer should be expanded
* @return The expanded buffer with any data from the input buffer
* copied in to it or the original buffer if there was no
* need for expansion
*/
public static ByteBuffer expand(ByteBuffer in, int newSize) {
if (in.capacity() >= newSize) {
return in;
}
ByteBuffer out;
boolean direct = false;
if (in.isDirect()) {
out = ByteBuffer.allocateDirect(newSize);
direct = true;
} else {
out = ByteBuffer.allocate(newSize);
}
// Copy data
in.flip();
out.put(in);
if (direct) {
cleanDirectBuffer(in);
}
return out;
}
public static void cleanDirectBuffer(ByteBuffer buf) {
if (cleanMethod != null) {
try {
cleanMethod.invoke(cleanerMethod.invoke(buf));
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException | SecurityException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("byteBufferUtils.cleaner"), e);
}
}
} else if (invokeCleanerMethod != null) {
try {
invokeCleanerMethod.invoke(unsafe, buf);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException | SecurityException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("byteBufferUtils.cleaner"), e);
}
}
}
}
}

View File

@@ -0,0 +1,834 @@
/*
* 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.tomcat.util.buf;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/*
* In a server it is very important to be able to operate on
* the original byte[] without converting everything to chars.
* Some protocols are ASCII only, and some allow different
* non-UNICODE encodings. The encoding is not known beforehand,
* and can even change during the execution of the protocol.
* ( for example a multipart message may have parts with different
* encoding )
*
* For HTTP it is not very clear how the encoding of RequestURI
* and mime values can be determined, but it is a great advantage
* to be able to parse the request without converting to string.
*/
// TODO: This class could either extend ByteBuffer, or better a ByteBuffer
// inside this way it could provide the search/etc on ByteBuffer, as a helper.
/**
* This class is used to represent a chunk of bytes, and utilities to manipulate
* byte[].
*
* The buffer can be modified and used for both input and output.
*
* There are 2 modes: The chunk can be associated with a sink - ByteInputChannel
* or ByteOutputChannel, which will be used when the buffer is empty (on input)
* or filled (on output). For output, it can also grow. This operating mode is
* selected by calling setLimit() or allocate(initial, limit) with limit != -1.
*
* Various search and append method are defined - similar with String and
* StringBuffer, but operating on bytes.
*
* This is important because it allows processing the http headers directly on
* the received bytes, without converting to chars and Strings until the strings
* are needed. In addition, the charset is determined later, from headers or
* user code.
*
* @author dac@sun.com
* @author James Todd [gonzo@sun.com]
* @author Costin Manolache
* @author Remy Maucherat
*/
public final class ByteChunk extends AbstractChunk {
private static final long serialVersionUID = 1L;
/**
* Input interface, used when the buffer is empty.
*
* Same as java.nio.channels.ReadableByteChannel
*/
public static interface ByteInputChannel {
/**
* Read new bytes.
*
* @return The number of bytes read
*
* @throws IOException If an I/O error occurs during reading
*/
public int realReadBytes() throws IOException;
}
/**
* When we need more space we'll either grow the buffer ( up to the limit )
* or send it to a channel.
*
* Same as java.nio.channel.WritableByteChannel.
*/
public static interface ByteOutputChannel {
/**
* Send the bytes ( usually the internal conversion buffer ). Expect 8k
* output if the buffer is full.
*
* @param buf bytes that will be written
* @param off offset in the bytes array
* @param len length that will be written
* @throws IOException If an I/O occurs while writing the bytes
*/
public void realWriteBytes(byte buf[], int off, int len) throws IOException;
/**
* Send the bytes ( usually the internal conversion buffer ). Expect 8k
* output if the buffer is full.
*
* @param from bytes that will be written
* @throws IOException If an I/O occurs while writing the bytes
*/
public void realWriteBytes(ByteBuffer from) throws IOException;
}
// --------------------
/**
* Default encoding used to convert to strings. It should be UTF8, as most
* standards seem to converge, but the servlet API requires 8859_1, and this
* object is used mostly for servlets.
*/
public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
private transient Charset charset;
// byte[]
private byte[] buff;
// transient as serialization is primarily for values via, e.g. JMX
private transient ByteInputChannel in = null;
private transient ByteOutputChannel out = null;
/**
* Creates a new, uninitialized ByteChunk object.
*/
public ByteChunk() {
}
public ByteChunk(int initial) {
allocate(initial, -1);
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeUTF(getCharset().name());
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
this.charset = Charset.forName(ois.readUTF());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public void recycle() {
super.recycle();
charset = null;
}
// -------------------- Setup --------------------
public void allocate(int initial, int limit) {
if (buff == null || buff.length < initial) {
buff = new byte[initial];
}
setLimit(limit);
start = 0;
end = 0;
isSet = true;
hasHashCode = false;
}
/**
* Sets the buffer to the specified subarray of bytes.
*
* @param b the ascii bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
buff = b;
start = off;
end = start + len;
isSet = true;
hasHashCode = false;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public Charset getCharset() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
return charset;
}
/**
* @return the buffer.
*/
public byte[] getBytes() {
return getBuffer();
}
/**
* @return the buffer.
*/
public byte[] getBuffer() {
return buff;
}
/**
* When the buffer is empty, read the data from the input channel.
*
* @param in The input channel
*/
public void setByteInputChannel(ByteInputChannel in) {
this.in = in;
}
/**
* When the buffer is full, write the data to the output channel. Also used
* when large amount of data is appended. If not set, the buffer will grow
* to the limit.
*
* @param out The output channel
*/
public void setByteOutputChannel(ByteOutputChannel out) {
this.out = out;
}
// -------------------- Adding data to the buffer --------------------
public void append(byte b) throws IOException {
makeSpace(1);
int limit = getLimitInternal();
// couldn't make space
if (end >= limit) {
flushBuffer();
}
buff[end++] = b;
}
public void append(ByteChunk src) throws IOException {
append(src.getBytes(), src.getStart(), src.getLength());
}
/**
* Add data to the buffer.
*
* @param src Bytes array
* @param off Offset
* @param len Length
* @throws IOException Writing overflow data to the output channel failed
*/
public void append(byte src[], int off, int len) throws IOException {
// will grow, up to limit
makeSpace(len);
int limit = getLimitInternal();
// Optimize on a common case.
// If the buffer is empty and the source is going to fill up all the
// space in buffer, may as well write it directly to the output,
// and avoid an extra copy
if (len == limit && end == start && out != null) {
out.realWriteBytes(src, off, len);
return;
}
// if we are below the limit
if (len <= limit - end) {
System.arraycopy(src, off, buff, end, len);
end += len;
return;
}
// Need more space than we can afford, need to flush buffer.
// The buffer is already at (or bigger than) limit.
// We chunk the data into slices fitting in the buffer limit, although
// if the data is written directly if it doesn't fit.
int avail = limit - end;
System.arraycopy(src, off, buff, end, avail);
end += avail;
flushBuffer();
int remain = len - avail;
while (remain > (limit - end)) {
out.realWriteBytes(src, (off + len) - remain, limit - end);
remain = remain - (limit - end);
}
System.arraycopy(src, (off + len) - remain, buff, end, remain);
end += remain;
}
/**
* Add data to the buffer.
*
* @param from the ByteBuffer with the data
* @throws IOException Writing overflow data to the output channel failed
*/
public void append(ByteBuffer from) throws IOException {
int len = from.remaining();
// will grow, up to limit
makeSpace(len);
int limit = getLimitInternal();
// Optimize on a common case.
// If the buffer is empty and the source is going to fill up all the
// space in buffer, may as well write it directly to the output,
// and avoid an extra copy
if (len == limit && end == start && out != null) {
out.realWriteBytes(from);
from.position(from.limit());
return;
}
// if we have limit and we're below
if (len <= limit - end) {
// makeSpace will grow the buffer to the limit,
// so we have space
from.get(buff, end, len);
end += len;
return;
}
// need more space than we can afford, need to flush
// buffer
// the buffer is already at ( or bigger than ) limit
// We chunk the data into slices fitting in the buffer limit, although
// if the data is written directly if it doesn't fit
int avail = limit - end;
from.get(buff, end, avail);
end += avail;
flushBuffer();
int fromLimit = from.limit();
int remain = len - avail;
avail = limit - end;
while (remain >= avail) {
from.limit(from.position() + avail);
out.realWriteBytes(from);
from.position(from.limit());
remain = remain - avail;
}
from.limit(fromLimit);
from.get(buff, end, remain);
end += remain;
}
// -------------------- Removing data from the buffer --------------------
public int substract() throws IOException {
if (checkEof()) {
return -1;
}
return buff[start++] & 0xFF;
}
public byte substractB() throws IOException {
if (checkEof()) {
return -1;
}
return buff[start++];
}
public int substract(byte dest[], int off, int len) throws IOException {
if (checkEof()) {
return -1;
}
int n = len;
if (len > getLength()) {
n = getLength();
}
System.arraycopy(buff, start, dest, off, n);
start += n;
return n;
}
/**
* Transfers bytes from the buffer to the specified ByteBuffer. After the
* operation the position of the ByteBuffer will be returned to the one
* before the operation, the limit will be the position incremented by the
* number of the transfered bytes.
*
* @param to the ByteBuffer into which bytes are to be written.
* @return an integer specifying the actual number of bytes read, or -1 if
* the end of the stream is reached
* @throws IOException if an input or output exception has occurred
*/
public int substract(ByteBuffer to) throws IOException {
if (checkEof()) {
return -1;
}
int n = Math.min(to.remaining(), getLength());
to.put(buff, start, n);
to.limit(to.position());
to.position(to.position() - n);
start += n;
return n;
}
private boolean checkEof() throws IOException {
if ((end - start) == 0) {
if (in == null) {
return true;
}
int n = in.realReadBytes();
if (n < 0) {
return true;
}
}
return false;
}
/**
* Send the buffer to the sink. Called by append() when the limit is
* reached. You can also call it explicitly to force the data to be written.
*
* @throws IOException Writing overflow data to the output channel failed
*/
public void flushBuffer() throws IOException {
// assert out!=null
if (out == null) {
throw new IOException("Buffer overflow, no sink " + getLimit() + " " + buff.length);
}
out.realWriteBytes(buff, start, end - start);
end = start;
}
/**
* Make space for len bytes. If len is small, allocate a reserve space too.
* Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}.
*
* @param count The size
*/
public void makeSpace(int count) {
byte[] tmp = null;
int limit = getLimitInternal();
long newSize;
long desiredSize = end + count;
// Can't grow above the limit
if (desiredSize > limit) {
desiredSize = limit;
}
if (buff == null) {
if (desiredSize < 256) {
desiredSize = 256; // take a minimum
}
buff = new byte[(int) desiredSize];
}
// limit < buf.length (the buffer is already big)
// or we already have space XXX
if (desiredSize <= buff.length) {
return;
}
// grow in larger chunks
if (desiredSize < 2L * buff.length) {
newSize = buff.length * 2L;
} else {
newSize = buff.length * 2L + count;
}
if (newSize > limit) {
newSize = limit;
}
tmp = new byte[(int) newSize];
// Compacts buffer
System.arraycopy(buff, start, tmp, 0, end - start);
buff = tmp;
tmp = null;
end = end - start;
start = 0;
}
// -------------------- Conversion and getters --------------------
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end - start == 0) {
return "";
}
return StringCache.toString(this);
}
public String toStringInternal() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
// new String(byte[], int, int, Charset) takes a defensive copy of the
// entire byte array. This is expensive if only a small subset of the
// bytes will be used. The code below is from Apache Harmony.
CharBuffer cb = charset.decode(ByteBuffer.wrap(buff, start, end - start));
return new String(cb.array(), cb.arrayOffset(), cb.length());
}
public long getLong() {
return Ascii.parseLong(buff, start, end - start);
}
// -------------------- equals --------------------
@Override
public boolean equals(Object obj) {
if (obj instanceof ByteChunk) {
return equals((ByteChunk) obj);
}
return false;
}
/**
* Compares the message bytes to the specified String object.
*
* @param s the String to compare
* @return <code>true</code> if the comparison succeeded, <code>false</code>
* otherwise
*/
public boolean equals(String s) {
// XXX ENCODING - this only works if encoding is UTF8-compat
// ( ok for tomcat, where we compare ascii - header names, etc )!!!
byte[] b = buff;
int len = end - start;
if (b == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (b[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Compares the message bytes to the specified String object.
*
* @param s the String to compare
* @return <code>true</code> if the comparison succeeded, <code>false</code>
* otherwise
*/
public boolean equalsIgnoreCase(String s) {
byte[] b = buff;
int len = end - start;
if (b == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (Ascii.toLower(b[off++]) != Ascii.toLower(s.charAt(i))) {
return false;
}
}
return true;
}
public boolean equals(ByteChunk bb) {
return equals(bb.getBytes(), bb.getStart(), bb.getLength());
}
public boolean equals(byte b2[], int off2, int len2) {
byte b1[] = buff;
if (b1 == null && b2 == null) {
return true;
}
int len = end - start;
if (len != len2 || b1 == null || b2 == null) {
return false;
}
int off1 = start;
while (len-- > 0) {
if (b1[off1++] != b2[off2++]) {
return false;
}
}
return true;
}
public boolean equals(CharChunk cc) {
return equals(cc.getChars(), cc.getStart(), cc.getLength());
}
public boolean equals(char c2[], int off2, int len2) {
// XXX works only for enc compatible with ASCII/UTF !!!
byte b1[] = buff;
if (c2 == null && b1 == null) {
return true;
}
if (b1 == null || c2 == null || end - start != len2) {
return false;
}
int off1 = start;
int len = end - start;
while (len-- > 0) {
if ((char) b1[off1++] != c2[off2++]) {
return false;
}
}
return true;
}
/**
* Returns true if the buffer starts with the specified string when tested
* in a case sensitive manner.
*
* @param s the string
* @param pos The position
*
* @return <code>true</code> if the start matches
*/
public boolean startsWith(String s, int pos) {
byte[] b = buff;
int len = s.length();
if (b == null || len + pos > end - start) {
return false;
}
int off = start + pos;
for (int i = 0; i < len; i++) {
if (b[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Returns true if the buffer starts with the specified string when tested
* in a case insensitive manner.
*
* @param s the string
* @param pos The position
*
* @return <code>true</code> if the start matches
*/
public boolean startsWithIgnoreCase(String s, int pos) {
byte[] b = buff;
int len = s.length();
if (b == null || len + pos > end - start) {
return false;
}
int off = start + pos;
for (int i = 0; i < len; i++) {
if (Ascii.toLower(b[off++]) != Ascii.toLower(s.charAt(i))) {
return false;
}
}
return true;
}
@Override
protected int getBufferElement(int index) {
return buff[index];
}
/**
* Returns the first instance of the given character in this ByteChunk
* starting at the specified byte. If the character is not found, -1 is
* returned. <br>
* NOTE: This only works for characters in the range 0-127.
*
* @param c The character
* @param starting The start position
* @return The position of the first instance of the character or -1 if the
* character is not found.
*/
public int indexOf(char c, int starting) {
int ret = indexOf(buff, start + starting, end, c);
return (ret >= start) ? ret - start : -1;
}
/**
* Returns the first instance of the given character in the given byte array
* between the specified start and end. <br>
* NOTE: This only works for characters in the range 0-127.
*
* @param bytes The array to search
* @param start The point to start searching from in the array
* @param end The point to stop searching in the array
* @param s The character to search for
* @return The position of the first instance of the character or -1 if the
* character is not found.
*/
public static int indexOf(byte bytes[], int start, int end, char s) {
int offset = start;
while (offset < end) {
byte b = bytes[offset];
if (b == s) {
return offset;
}
offset++;
}
return -1;
}
/**
* Returns the first instance of the given byte in the byte array between
* the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The byte to search for
* @return The position of the first instance of the byte or -1 if the byte
* is not found.
*/
public static int findByte(byte bytes[], int start, int end, byte b) {
int offset = start;
while (offset < end) {
if (bytes[offset] == b) {
return offset;
}
offset++;
}
return -1;
}
/**
* Returns the first instance of any of the given bytes in the byte array
* between the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The array of bytes to search for
* @return The position of the first instance of the byte or -1 if the byte
* is not found.
*/
public static int findBytes(byte bytes[], int start, int end, byte b[]) {
int blen = b.length;
int offset = start;
while (offset < end) {
for (int i = 0; i < blen; i++) {
if (bytes[offset] == b[i]) {
return offset;
}
}
offset++;
}
return -1;
}
/**
* Convert specified String to a byte array. This ONLY WORKS for ascii, UTF
* chars will be truncated.
*
* @param value to convert to byte array
* @return the byte array value
*/
public static final byte[] convertToBytes(String value) {
byte[] result = new byte[value.length()];
for (int i = 0; i < value.length(); i++) {
result[i] = (byte) value.charAt(i);
}
return result;
}
}

View File

@@ -0,0 +1,192 @@
/*
* 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.tomcat.util.buf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
/**
* NIO based character encoder.
*/
public final class C2BConverter {
private final CharsetEncoder encoder;
private ByteBuffer bb = null;
private CharBuffer cb = null;
/**
* Leftover buffer used for multi-characters characters.
*/
private final CharBuffer leftovers;
public C2BConverter(Charset charset) {
encoder = charset.newEncoder();
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE)
.onMalformedInput(CodingErrorAction.REPLACE);
char[] left = new char[4];
leftovers = CharBuffer.wrap(left);
}
/**
* Reset the encoder state.
*/
public void recycle() {
encoder.reset();
leftovers.position(0);
}
public boolean isUndeflow() {
return (leftovers.position() > 0);
}
/**
* Convert the given characters to bytes.
*
* @param cc char input
* @param bc byte output
* @throws IOException An encoding error occurred
*/
public void convert(CharChunk cc, ByteChunk bc) throws IOException {
if ((bb == null) || (bb.array() != bc.getBuffer())) {
// Create a new byte buffer if anything changed
bb = ByteBuffer.wrap(bc.getBuffer(), bc.getEnd(), bc.getBuffer().length - bc.getEnd());
} else {
// Initialize the byte buffer
bb.limit(bc.getBuffer().length);
bb.position(bc.getEnd());
}
if ((cb == null) || (cb.array() != cc.getBuffer())) {
// Create a new char buffer if anything changed
cb = CharBuffer.wrap(cc.getBuffer(), cc.getStart(), cc.getLength());
} else {
// Initialize the char buffer
cb.limit(cc.getEnd());
cb.position(cc.getStart());
}
CoderResult result = null;
// Parse leftover if any are present
if (leftovers.position() > 0) {
int pos = bb.position();
// Loop until one char is encoded or there is a encoder error
do {
leftovers.put((char) cc.substract());
leftovers.flip();
result = encoder.encode(leftovers, bb, false);
leftovers.position(leftovers.limit());
leftovers.limit(leftovers.array().length);
} while (result.isUnderflow() && (bb.position() == pos));
if (result.isError() || result.isMalformed()) {
result.throwException();
}
cb.position(cc.getStart());
leftovers.position(0);
}
// Do the decoding and get the results into the byte chunk and the char
// chunk
result = encoder.encode(cb, bb, false);
if (result.isError() || result.isMalformed()) {
result.throwException();
} else if (result.isOverflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.setEnd(bb.position());
cc.setOffset(cb.position());
} else if (result.isUnderflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.setEnd(bb.position());
cc.setOffset(cb.position());
// Put leftovers in the leftovers char buffer
if (cc.getLength() > 0) {
leftovers.limit(leftovers.array().length);
leftovers.position(cc.getLength());
cc.substract(leftovers.array(), 0, cc.getLength());
}
}
}
/**
* Convert the given characters to bytes.
*
* @param cc char input
* @param bc byte output
* @throws IOException An encoding error occurred
*/
public void convert(CharBuffer cc, ByteBuffer bc) throws IOException {
if ((bb == null) || (bb.array() != bc.array())) {
// Create a new byte buffer if anything changed
bb = ByteBuffer.wrap(bc.array(), bc.limit(), bc.capacity() - bc.limit());
} else {
// Initialize the byte buffer
bb.limit(bc.capacity());
bb.position(bc.limit());
}
if ((cb == null) || (cb.array() != cc.array())) {
// Create a new char buffer if anything changed
cb = CharBuffer.wrap(cc.array(), cc.arrayOffset() + cc.position(), cc.remaining());
} else {
// Initialize the char buffer
cb.limit(cc.limit());
cb.position(cc.position());
}
CoderResult result = null;
// Parse leftover if any are present
if (leftovers.position() > 0) {
int pos = bb.position();
// Loop until one char is encoded or there is a encoder error
do {
leftovers.put(cc.get());
leftovers.flip();
result = encoder.encode(leftovers, bb, false);
leftovers.position(leftovers.limit());
leftovers.limit(leftovers.array().length);
} while (result.isUnderflow() && (bb.position() == pos));
if (result.isError() || result.isMalformed()) {
result.throwException();
}
cb.position(cc.position());
leftovers.position(0);
}
// Do the decoding and get the results into the byte chunk and the char
// chunk
result = encoder.encode(cb, bb, false);
if (result.isError() || result.isMalformed()) {
result.throwException();
} else if (result.isOverflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.limit(bb.position());
cc.position(cb.position());
} else if (result.isUnderflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.limit(bb.position());
cc.position(cb.position());
// Put leftovers in the leftovers char buffer
if (cc.remaining() > 0) {
leftovers.limit(leftovers.array().length);
leftovers.position(cc.remaining());
cc.get(leftovers.array(), 0, cc.remaining());
}
}
}
public Charset getCharset() {
return encoder.charset();
}
}

View File

@@ -0,0 +1,662 @@
/*
* 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.tomcat.util.buf;
import java.io.IOException;
/**
* Utilities to manipulate char chunks. While String is the easiest way to
* manipulate chars ( search, substrings, etc), it is known to not be the most
* efficient solution - Strings are designed as immutable and secure objects.
*
* @author dac@sun.com
* @author James Todd [gonzo@sun.com]
* @author Costin Manolache
* @author Remy Maucherat
*/
public final class CharChunk extends AbstractChunk implements CharSequence {
private static final long serialVersionUID = 1L;
/**
* Input interface, used when the buffer is empty.
*/
public static interface CharInputChannel {
/**
* Read new characters.
*
* @return The number of characters read
*
* @throws IOException If an I/O error occurs during reading
*/
public int realReadChars() throws IOException;
}
/**
* When we need more space we'll either grow the buffer ( up to the limit )
* or send it to a channel.
*/
public static interface CharOutputChannel {
/**
* Send the bytes ( usually the internal conversion buffer ). Expect 8k
* output if the buffer is full.
*
* @param buf characters that will be written
* @param off offset in the characters array
* @param len length that will be written
* @throws IOException If an I/O occurs while writing the characters
*/
public void realWriteChars(char buf[], int off, int len) throws IOException;
}
// --------------------
// char[]
private char[] buff;
// transient as serialization is primarily for values via, e.g. JMX
private transient CharInputChannel in = null;
private transient CharOutputChannel out = null;
/**
* Creates a new, uninitialized CharChunk object.
*/
public CharChunk() {
}
public CharChunk(int initial) {
allocate(initial, -1);
}
// --------------------
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// -------------------- Setup --------------------
public void allocate(int initial, int limit) {
if (buff == null || buff.length < initial) {
buff = new char[initial];
}
setLimit(limit);
start = 0;
end = 0;
isSet = true;
hasHashCode = false;
}
/**
* Sets the buffer to the specified subarray of characters.
*
* @param c the characters
* @param off the start offset of the characters
* @param len the length of the characters
*/
public void setChars(char[] c, int off, int len) {
buff = c;
start = off;
end = start + len;
isSet = true;
hasHashCode = false;
}
/**
* @return the buffer.
*/
public char[] getChars() {
return getBuffer();
}
/**
* @return the buffer.
*/
public char[] getBuffer() {
return buff;
}
/**
* When the buffer is empty, read the data from the input channel.
*
* @param in The input channel
*/
public void setCharInputChannel(CharInputChannel in) {
this.in = in;
}
/**
* When the buffer is full, write the data to the output channel. Also used
* when large amount of data is appended. If not set, the buffer will grow
* to the limit.
*
* @param out The output channel
*/
public void setCharOutputChannel(CharOutputChannel out) {
this.out = out;
}
// -------------------- Adding data to the buffer --------------------
public void append(char c) throws IOException {
makeSpace(1);
int limit = getLimitInternal();
// couldn't make space
if (end >= limit) {
flushBuffer();
}
buff[end++] = c;
}
public void append(CharChunk src) throws IOException {
append(src.getBuffer(), src.getOffset(), src.getLength());
}
/**
* Add data to the buffer.
*
* @param src Char array
* @param off Offset
* @param len Length
* @throws IOException Writing overflow data to the output channel failed
*/
public void append(char src[], int off, int len) throws IOException {
// will grow, up to limit
makeSpace(len);
int limit = getLimitInternal();
// Optimize on a common case.
// If the buffer is empty and the source is going to fill up all the
// space in buffer, may as well write it directly to the output,
// and avoid an extra copy
if (len == limit && end == start && out != null) {
out.realWriteChars(src, off, len);
return;
}
// if we are below the limit
if (len <= limit - end) {
System.arraycopy(src, off, buff, end, len);
end += len;
return;
}
// Need more space than we can afford, need to flush buffer.
// The buffer is already at (or bigger than) limit.
// Optimization:
// If len-avail < length (i.e. after we fill the buffer with what we
// can, the remaining will fit in the buffer) we'll just copy the first
// part, flush, then copy the second part - 1 write and still have some
// space for more. We'll still have 2 writes, but we write more on the first.
if (len + end < 2 * limit) {
/*
* If the request length exceeds the size of the output buffer,
* flush the output buffer and then write the data directly. We
* can't avoid 2 writes, but we can write more on the second
*/
int avail = limit - end;
System.arraycopy(src, off, buff, end, avail);
end += avail;
flushBuffer();
System.arraycopy(src, off + avail, buff, end, len - avail);
end += len - avail;
} else { // len > buf.length + avail
// long write - flush the buffer and write the rest
// directly from source
flushBuffer();
out.realWriteChars(src, off, len);
}
}
/**
* Append a string to the buffer.
*
* @param s The string
* @throws IOException Writing overflow data to the output channel failed
*/
public void append(String s) throws IOException {
append(s, 0, s.length());
}
/**
* Append a string to the buffer.
*
* @param s The string
* @param off Offset
* @param len Length
* @throws IOException Writing overflow data to the output channel failed
*/
public void append(String s, int off, int len) throws IOException {
if (s == null) {
return;
}
// will grow, up to limit
makeSpace(len);
int limit = getLimitInternal();
int sOff = off;
int sEnd = off + len;
while (sOff < sEnd) {
int d = min(limit - end, sEnd - sOff);
s.getChars(sOff, sOff + d, buff, end);
sOff += d;
end += d;
if (end >= limit) {
flushBuffer();
}
}
}
// -------------------- Removing data from the buffer --------------------
public int substract() throws IOException {
if (checkEof()) {
return -1;
}
return buff[start++];
}
public int substract(char dest[], int off, int len) throws IOException {
if (checkEof()) {
return -1;
}
int n = len;
if (len > getLength()) {
n = getLength();
}
System.arraycopy(buff, start, dest, off, n);
start += n;
return n;
}
private boolean checkEof() throws IOException {
if ((end - start) == 0) {
if (in == null) {
return true;
}
int n = in.realReadChars();
if (n < 0) {
return true;
}
}
return false;
}
/**
* Send the buffer to the sink. Called by append() when the limit is
* reached. You can also call it explicitly to force the data to be written.
*
* @throws IOException Writing overflow data to the output channel failed
*/
public void flushBuffer() throws IOException {
// assert out!=null
if (out == null) {
throw new IOException("Buffer overflow, no sink " + getLimit() + " " + buff.length);
}
out.realWriteChars(buff, start, end - start);
end = start;
}
/**
* Make space for len chars. If len is small, allocate a reserve space too.
* Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}.
*
* @param count The size
*/
public void makeSpace(int count) {
char[] tmp = null;
int limit = getLimitInternal();
long newSize;
long desiredSize = end + count;
// Can't grow above the limit
if (desiredSize > limit) {
desiredSize = limit;
}
if (buff == null) {
if (desiredSize < 256) {
desiredSize = 256; // take a minimum
}
buff = new char[(int) desiredSize];
}
// limit < buf.length (the buffer is already big)
// or we already have space XXX
if (desiredSize <= buff.length) {
return;
}
// grow in larger chunks
if (desiredSize < 2L * buff.length) {
newSize = buff.length * 2L;
} else {
newSize = buff.length * 2L + count;
}
if (newSize > limit) {
newSize = limit;
}
tmp = new char[(int) newSize];
// Some calling code assumes buffer will not be compacted
System.arraycopy(buff, 0, tmp, 0, end);
buff = tmp;
tmp = null;
}
// -------------------- Conversion and getters --------------------
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end - start == 0) {
return "";
}
return StringCache.toString(this);
}
public String toStringInternal() {
return new String(buff, start, end - start);
}
// -------------------- equals --------------------
@Override
public boolean equals(Object obj) {
if (obj instanceof CharChunk) {
return equals((CharChunk) obj);
}
return false;
}
/**
* Compares the message bytes to the specified String object.
*
* @param s the String to compare
* @return <code>true</code> if the comparison succeeded, <code>false</code>
* otherwise
*/
public boolean equals(String s) {
char[] c = buff;
int len = end - start;
if (c == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Compares the message bytes to the specified String object.
*
* @param s the String to compare
* @return <code>true</code> if the comparison succeeded, <code>false</code>
* otherwise
*/
public boolean equalsIgnoreCase(String s) {
char[] c = buff;
int len = end - start;
if (c == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
return false;
}
}
return true;
}
public boolean equals(CharChunk cc) {
return equals(cc.getChars(), cc.getOffset(), cc.getLength());
}
public boolean equals(char b2[], int off2, int len2) {
char b1[] = buff;
if (b1 == null && b2 == null) {
return true;
}
int len = end - start;
if (len != len2 || b1 == null || b2 == null) {
return false;
}
int off1 = start;
while (len-- > 0) {
if (b1[off1++] != b2[off2++]) {
return false;
}
}
return true;
}
/**
* @return <code>true</code> if the message bytes starts with the specified
* string.
* @param s The string
*/
public boolean startsWith(String s) {
char[] c = buff;
int len = s.length();
if (c == null || len > end - start) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Returns true if the buffer starts with the specified string.
*
* @param s the string
* @param pos The position
*
* @return <code>true</code> if the start matches
*/
public boolean startsWithIgnoreCase(String s, int pos) {
char[] c = buff;
int len = s.length();
if (c == null || len + pos > end - start) {
return false;
}
int off = start + pos;
for (int i = 0; i < len; i++) {
if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
return false;
}
}
return true;
}
/**
* @return <code>true</code> if the message bytes end with the specified
* string.
* @param s The string
*/
public boolean endsWith(String s) {
char[] c = buff;
int len = s.length();
if (c == null || len > end - start) {
return false;
}
int off = end - len;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
@Override
protected int getBufferElement(int index) {
return buff[index];
}
public int indexOf(char c) {
return indexOf(c, start);
}
/**
* Returns the first instance of the given character in this CharChunk
* starting at the specified char. If the character is not found, -1 is
* returned. <br>
*
* @param c The character
* @param starting The start position
* @return The position of the first instance of the character or -1 if the
* character is not found.
*/
public int indexOf(char c, int starting) {
int ret = indexOf(buff, start + starting, end, c);
return (ret >= start) ? ret - start : -1;
}
/**
* Returns the first instance of the given character in the given char array
* between the specified start and end. <br>
*
* @param chars The array to search
* @param start The point to start searching from in the array
* @param end The point to stop searching in the array
* @param s The character to search for
* @return The position of the first instance of the character or -1 if the
* character is not found.
*/
public static int indexOf(char chars[], int start, int end, char s) {
int offset = start;
while (offset < end) {
char c = chars[offset];
if (c == s) {
return offset;
}
offset++;
}
return -1;
}
// -------------------- utils
private int min(int a, int b) {
if (a < b) {
return a;
}
return b;
}
// Char sequence impl
@Override
public char charAt(int index) {
return buff[index + start];
}
@Override
public CharSequence subSequence(int start, int end) {
try {
CharChunk result = (CharChunk) this.clone();
result.setOffset(this.start + start);
result.setEnd(this.start + end);
return result;
} catch (CloneNotSupportedException e) {
// Cannot happen
return null;
}
}
@Override
public int length() {
return end - start;
}
/**
* NO-OP.
*
* @param optimizedWrite Ignored
*
* @deprecated Unused code. This is now a NO-OP and will be removed without
* replacement in Tomcat 10.
*/
@Deprecated
public void setOptimizedWrite(boolean optimizedWrite) {
}
}

View File

@@ -0,0 +1,230 @@
/*
* 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.tomcat.util.buf;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class CharsetCache {
/* Note: Package private to enable testing without reflection */
static final String[] INITIAL_CHARSETS = new String[] { "iso-8859-1", "utf-8" };
/*
* Note: Package private to enable testing without reflection
*/
static final String[] LAZY_CHARSETS = new String[] {
// Initial set from Oracle JDK 8 u192
"037", "1006", "1025", "1026", "1046", "1047", "1089", "1097", "1098", "1112", "1122", "1123", "1124",
"1140", "1141", "1142", "1143", "1144", "1145", "1146", "1147", "1148", "1149", "1166", "1364", "1381",
"1383", "273", "277", "278", "280", "284", "285", "290", "297", "300", "33722", "420", "424", "437", "500",
"5601", "646", "737", "775", "813", "834", "838", "850", "852", "855", "856", "857", "858", "860", "861",
"862", "863", "864", "865", "866", "868", "869", "870", "871", "874", "875", "8859_13", "8859_15", "8859_2",
"8859_3", "8859_4", "8859_5", "8859_6", "8859_7", "8859_8", "8859_9", "912", "913", "914", "915", "916",
"918", "920", "921", "922", "923", "930", "933", "935", "937", "939", "942", "942c", "943", "943c", "948",
"949", "949c", "950", "964", "970", "ansi-1251", "ansi_x3.4-1968", "ansi_x3.4-1986", "arabic", "ascii",
"ascii7", "asmo-708", "big5", "big5-hkscs", "big5-hkscs", "big5-hkscs-2001", "big5-hkscs:unicode3.0",
"big5_hkscs", "big5_hkscs_2001", "big5_solaris", "big5hk", "big5hk-2001", "big5hkscs", "big5hkscs-2001",
"ccsid00858", "ccsid01140", "ccsid01141", "ccsid01142", "ccsid01143", "ccsid01144", "ccsid01145",
"ccsid01146", "ccsid01147", "ccsid01148", "ccsid01149", "cesu-8", "cesu8", "cns11643", "compound_text",
"cp-ar", "cp-gr", "cp-is", "cp00858", "cp01140", "cp01141", "cp01142", "cp01143", "cp01144", "cp01145",
"cp01146", "cp01147", "cp01148", "cp01149", "cp037", "cp1006", "cp1025", "cp1026", "cp1046", "cp1047",
"cp1089", "cp1097", "cp1098", "cp1112", "cp1122", "cp1123", "cp1124", "cp1140", "cp1141", "cp1142",
"cp1143", "cp1144", "cp1145", "cp1146", "cp1147", "cp1148", "cp1149", "cp1166", "cp1250", "cp1251",
"cp1252", "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp1364", "cp1381", "cp1383", "cp273",
"cp277", "cp278", "cp280", "cp284", "cp285", "cp290", "cp297", "cp300", "cp33722", "cp367", "cp420",
"cp424", "cp437", "cp500", "cp50220", "cp50221", "cp5346", "cp5347", "cp5348", "cp5349", "cp5350", "cp5353",
"cp737", "cp775", "cp813", "cp833", "cp834", "cp838", "cp850", "cp852", "cp855", "cp856", "cp857", "cp858",
"cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866", "cp868", "cp869", "cp870", "cp871", "cp874",
"cp875", "cp912", "cp913", "cp914", "cp915", "cp916", "cp918", "cp920", "cp921", "cp922", "cp923", "cp930",
"cp933", "cp935", "cp936", "cp937", "cp939", "cp942", "cp942c", "cp943", "cp943c", "cp948", "cp949",
"cp949c", "cp950", "cp964", "cp970", "cpibm284", "cpibm285", "cpibm297", "cpibm37", "cs-ebcdic-cp-ca",
"cs-ebcdic-cp-nl", "cs-ebcdic-cp-us", "cs-ebcdic-cp-wt", "csascii", "csbig5", "cscesu-8", "cseuckr",
"cseucpkdfmtjapanese", "cshalfwidthkatakana", "csibm037", "csibm278", "csibm284", "csibm285", "csibm290",
"csibm297", "csibm420", "csibm424", "csibm500", "csibm857", "csibm860", "csibm861", "csibm862", "csibm863",
"csibm864", "csibm865", "csibm866", "csibm868", "csibm869", "csibm870", "csibm871", "csiso153gost1976874",
"csiso159jisx02121990", "csiso2022cn", "csiso2022jp", "csiso2022jp2", "csiso2022kr", "csiso87jisx0208",
"csisolatin0", "csisolatin2", "csisolatin3", "csisolatin4", "csisolatin5", "csisolatin9",
"csisolatinarabic", "csisolatincyrillic", "csisolatingreek", "csisolatinhebrew", "csjisencoding", "cskoi8r",
"cspc850multilingual", "cspc862latinhebrew", "cspc8codepage437", "cspcp852", "cspcp855", "csshiftjis",
"cswindows31j", "cyrillic", "default", "ebcdic-cp-ar1", "ebcdic-cp-ar2", "ebcdic-cp-bh", "ebcdic-cp-ca",
"ebcdic-cp-ch", "ebcdic-cp-fr", "ebcdic-cp-gb", "ebcdic-cp-he", "ebcdic-cp-is", "ebcdic-cp-nl",
"ebcdic-cp-roece", "ebcdic-cp-se", "ebcdic-cp-us", "ebcdic-cp-wt", "ebcdic-cp-yu", "ebcdic-de-273+euro",
"ebcdic-dk-277+euro", "ebcdic-es-284+euro", "ebcdic-fi-278+euro", "ebcdic-fr-277+euro", "ebcdic-gb",
"ebcdic-gb-285+euro", "ebcdic-international-500+euro", "ebcdic-it-280+euro", "ebcdic-jp-kana",
"ebcdic-no-277+euro", "ebcdic-s-871+euro", "ebcdic-se-278+euro", "ebcdic-sv", "ebcdic-us-037+euro",
"ecma-114", "ecma-118", "elot_928", "euc-cn", "euc-jp", "euc-jp-linux", "euc-kr", "euc-tw", "euc_cn",
"euc_jp", "euc_jp_linux", "euc_jp_solaris", "euc_kr", "euc_tw", "euccn", "eucjis", "eucjp", "eucjp-open",
"euckr", "euctw", "extended_unix_code_packed_format_for_japanese", "gb18030", "gb18030-2000", "gb2312",
"gb2312", "gb2312-1980", "gb2312-80", "gbk", "greek", "greek8", "hebrew", "ibm-037", "ibm-1006", "ibm-1025",
"ibm-1026", "ibm-1046", "ibm-1047", "ibm-1089", "ibm-1097", "ibm-1098", "ibm-1112", "ibm-1122", "ibm-1123",
"ibm-1124", "ibm-1166", "ibm-1364", "ibm-1381", "ibm-1383", "ibm-273", "ibm-277", "ibm-278", "ibm-280",
"ibm-284", "ibm-285", "ibm-290", "ibm-297", "ibm-300", "ibm-33722", "ibm-33722_vascii_vpua", "ibm-37",
"ibm-420", "ibm-424", "ibm-437", "ibm-500", "ibm-5050", "ibm-737", "ibm-775", "ibm-813", "ibm-833",
"ibm-834", "ibm-838", "ibm-850", "ibm-852", "ibm-855", "ibm-856", "ibm-857", "ibm-860", "ibm-861",
"ibm-862", "ibm-863", "ibm-864", "ibm-865", "ibm-866", "ibm-868", "ibm-869", "ibm-870", "ibm-871",
"ibm-874", "ibm-875", "ibm-912", "ibm-913", "ibm-914", "ibm-915", "ibm-916", "ibm-918", "ibm-920",
"ibm-921", "ibm-922", "ibm-923", "ibm-930", "ibm-933", "ibm-935", "ibm-937", "ibm-939", "ibm-942",
"ibm-942c", "ibm-943", "ibm-943c", "ibm-948", "ibm-949", "ibm-949c", "ibm-950", "ibm-964", "ibm-970",
"ibm-euckr", "ibm-thai", "ibm00858", "ibm01140", "ibm01141", "ibm01142", "ibm01143", "ibm01144", "ibm01145",
"ibm01146", "ibm01147", "ibm01148", "ibm01149", "ibm037", "ibm037", "ibm1006", "ibm1025", "ibm1026",
"ibm1026", "ibm1046", "ibm1047", "ibm1089", "ibm1097", "ibm1098", "ibm1112", "ibm1122", "ibm1123",
"ibm1124", "ibm1166", "ibm1364", "ibm1381", "ibm1383", "ibm273", "ibm273", "ibm277", "ibm277", "ibm278",
"ibm278", "ibm280", "ibm280", "ibm284", "ibm284", "ibm285", "ibm285", "ibm290", "ibm290", "ibm297",
"ibm297", "ibm300", "ibm33722", "ibm367", "ibm420", "ibm420", "ibm424", "ibm424", "ibm437", "ibm437",
"ibm500", "ibm500", "ibm737", "ibm775", "ibm775", "ibm813", "ibm833", "ibm834", "ibm838", "ibm850",
"ibm850", "ibm852", "ibm852", "ibm855", "ibm855", "ibm856", "ibm857", "ibm857", "ibm860", "ibm860",
"ibm861", "ibm861", "ibm862", "ibm862", "ibm863", "ibm863", "ibm864", "ibm864", "ibm865", "ibm865",
"ibm866", "ibm866", "ibm868", "ibm868", "ibm869", "ibm869", "ibm870", "ibm870", "ibm871", "ibm871",
"ibm874", "ibm875", "ibm912", "ibm913", "ibm914", "ibm915", "ibm916", "ibm918", "ibm920", "ibm921",
"ibm922", "ibm923", "ibm930", "ibm933", "ibm935", "ibm937", "ibm939", "ibm942", "ibm942c", "ibm943",
"ibm943c", "ibm948", "ibm949", "ibm949c", "ibm950", "ibm964", "ibm970", "iscii", "iscii91",
"iso-10646-ucs-2", "iso-2022-cn", "iso-2022-cn-cns", "iso-2022-cn-gb", "iso-2022-jp", "iso-2022-jp-2",
"iso-2022-kr", "iso-8859-11", "iso-8859-13", "iso-8859-15", "iso-8859-15", "iso-8859-2", "iso-8859-3",
"iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-9", "iso-ir-101",
"iso-ir-109", "iso-ir-110", "iso-ir-126", "iso-ir-127", "iso-ir-138", "iso-ir-144", "iso-ir-148",
"iso-ir-153", "iso-ir-159", "iso-ir-6", "iso-ir-87", "iso2022cn", "iso2022cn_cns", "iso2022cn_gb",
"iso2022jp", "iso2022jp2", "iso2022kr", "iso646-us", "iso8859-13", "iso8859-15", "iso8859-2", "iso8859-3",
"iso8859-4", "iso8859-5", "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859_11", "iso8859_13",
"iso8859_15", "iso8859_15_fdis", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5", "iso8859_6",
"iso8859_7", "iso8859_8", "iso8859_9", "iso_646.irv:1983", "iso_646.irv:1991", "iso_8859-13", "iso_8859-15",
"iso_8859-2", "iso_8859-2:1987", "iso_8859-3", "iso_8859-3:1988", "iso_8859-4", "iso_8859-4:1988",
"iso_8859-5", "iso_8859-5:1988", "iso_8859-6", "iso_8859-6:1987", "iso_8859-7", "iso_8859-7:1987",
"iso_8859-8", "iso_8859-8:1988", "iso_8859-9", "iso_8859-9:1989", "jis", "jis0201", "jis0208", "jis0212",
"jis_c6226-1983", "jis_encoding", "jis_x0201", "jis_x0201", "jis_x0208-1983", "jis_x0212-1990",
"jis_x0212-1990", "jisautodetect", "johab", "koi8", "koi8-r", "koi8-u", "koi8_r", "koi8_u",
"ks_c_5601-1987", "ksc5601", "ksc5601-1987", "ksc5601-1992", "ksc5601_1987", "ksc5601_1992", "ksc_5601",
"l2", "l3", "l4", "l5", "l9", "latin0", "latin2", "latin3", "latin4", "latin5", "latin9", "macarabic",
"maccentraleurope", "maccroatian", "maccyrillic", "macdingbat", "macgreek", "machebrew", "maciceland",
"macroman", "macromania", "macsymbol", "macthai", "macturkish", "macukraine", "ms-874", "ms1361", "ms50220",
"ms50221", "ms874", "ms932", "ms936", "ms949", "ms950", "ms950_hkscs", "ms950_hkscs_xp", "ms_936", "ms_949",
"ms_kanji", "pc-multilingual-850+euro", "pck", "shift-jis", "shift_jis", "shift_jis", "sjis",
"st_sev_358-88", "sun_eu_greek", "tis-620", "tis620", "tis620.2533", "unicode", "unicodebig",
"unicodebigunmarked", "unicodelittle", "unicodelittleunmarked", "us", "us-ascii", "utf-16", "utf-16be",
"utf-16le", "utf-32", "utf-32be", "utf-32be-bom", "utf-32le", "utf-32le-bom", "utf16", "utf32", "utf_16",
"utf_16be", "utf_16le", "utf_32", "utf_32be", "utf_32be_bom", "utf_32le", "utf_32le_bom", "windows-1250",
"windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256",
"windows-1257", "windows-1258", "windows-31j", "windows-437", "windows-874", "windows-932", "windows-936",
"windows-949", "windows-950", "windows-iso2022jp", "windows949", "x-big5-hkscs-2001", "x-big5-solaris",
"x-compound-text", "x-compound_text", "x-euc-cn", "x-euc-jp", "x-euc-jp-linux", "x-euc-tw", "x-eucjp",
"x-eucjp-open", "x-ibm1006", "x-ibm1025", "x-ibm1046", "x-ibm1097", "x-ibm1098", "x-ibm1112", "x-ibm1122",
"x-ibm1123", "x-ibm1124", "x-ibm1166", "x-ibm1364", "x-ibm1381", "x-ibm1383", "x-ibm300", "x-ibm33722",
"x-ibm737", "x-ibm833", "x-ibm834", "x-ibm856", "x-ibm874", "x-ibm875", "x-ibm921", "x-ibm922", "x-ibm930",
"x-ibm933", "x-ibm935", "x-ibm937", "x-ibm939", "x-ibm942", "x-ibm942c", "x-ibm943", "x-ibm943c",
"x-ibm948", "x-ibm949", "x-ibm949c", "x-ibm950", "x-ibm964", "x-ibm970", "x-iscii91", "x-iso-2022-cn-cns",
"x-iso-2022-cn-gb", "x-iso-8859-11", "x-jis0208", "x-jisautodetect", "x-johab", "x-macarabic",
"x-maccentraleurope", "x-maccroatian", "x-maccyrillic", "x-macdingbat", "x-macgreek", "x-machebrew",
"x-maciceland", "x-macroman", "x-macromania", "x-macsymbol", "x-macthai", "x-macturkish", "x-macukraine",
"x-ms932_0213", "x-ms950-hkscs", "x-ms950-hkscs-xp", "x-mswin-936", "x-pck", "x-sjis", "x-sjis_0213",
"x-utf-16be", "x-utf-16le", "x-utf-16le-bom", "x-utf-32be", "x-utf-32be-bom", "x-utf-32le",
"x-utf-32le-bom", "x-windows-50220", "x-windows-50221", "x-windows-874", "x-windows-949", "x-windows-950",
"x-windows-iso2022jp", "x0201", "x0208", "x0212", "x11-compound_text",
// Added from Oracle JDK 10.0.2
"csiso885915", "csiso885916", "iso-8859-16", "iso-ir-226", "iso_8859-16", "iso_8859-16:2001", "l10",
"latin-9", "latin10", "ms932-0213", "ms932:2004", "ms932_0213", "shift_jis:2004", "shift_jis_0213:2004",
"sjis-0213", "sjis:2004", "sjis_0213", "sjis_0213:2004", "windows-932-0213", "windows-932:2004",
// Added from OpenJDK 11.0.1
"932", "cp932", "cpeuccn", "ibm-1252", "ibm-932", "ibm-euccn", "ibm1252", "ibm932", "ibmeuccn", "x-ibm932",
// Added from OpenJDK 12 ea28
"1129", "cp1129", "ibm-1129", "ibm-euctw", "ibm1129", "x-ibm1129",
// Added from OpenJDK 13 ea15
"29626c", "833", "cp29626c", "ibm-1140", "ibm-1141", "ibm-1142", "ibm-1143", "ibm-1144", "ibm-1145",
"ibm-1146", "ibm-1147", "ibm-1148", "ibm-1149", "ibm-29626c", "ibm-858", "ibm-eucjp", "ibm1140", "ibm1141",
"ibm1142", "ibm1143", "ibm1144", "ibm1145", "ibm1146", "ibm1147", "ibm1148", "ibm1149", "ibm29626c",
"ibm858", "x-ibm29626c",
// Added from HPE JVM 1.8.0.17-hp-ux
"cp1051", "cp1386", "cshproman8", "hp-roman8", "ibm-1051", "r8", "roman8", "roman9"
};
private static final Charset DUMMY_CHARSET = new DummyCharset("Dummy", null);
private ConcurrentMap<String,Charset> cache = new ConcurrentHashMap<>();
public CharsetCache() {
// Pre-populate the cache
for (String charsetName : INITIAL_CHARSETS) {
Charset charset = Charset.forName(charsetName);
addToCache(charsetName, charset);
}
for (String charsetName : LAZY_CHARSETS) {
addToCache(charsetName, DUMMY_CHARSET);
}
}
private void addToCache(String name, Charset charset) {
cache.put(name, charset);
for (String alias : charset.aliases()) {
cache.put(alias.toLowerCase(Locale.ENGLISH), charset);
}
}
public Charset getCharset(String charsetName) {
String lcCharsetName = charsetName.toLowerCase(Locale.ENGLISH);
Charset result = cache.get(lcCharsetName);
if (result == DUMMY_CHARSET) {
// Name is known but the Charset is not in the cache
Charset charset = Charset.forName(lcCharsetName);
if (charset == null) {
// Charset not available in this JVM - remove cache entry
cache.remove(lcCharsetName);
result = null;
} else {
// Charset is available - populate cache entry
addToCache(lcCharsetName, charset);
result = charset;
}
}
return result;
}
/*
* Placeholder Charset implementation for entries that will be loaded lazily
* into the cache.
*/
private static class DummyCharset extends Charset {
protected DummyCharset(String canonicalName, String[] aliases) {
super(canonicalName, aliases);
}
@Override
public boolean contains(Charset cs) {
return false;
}
@Override
public CharsetDecoder newDecoder() {
return null;
}
@Override
public CharsetEncoder newEncoder() {
return null;
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.tomcat.util.buf;
/**
* String constants for the file package.
*/
public final class Constants {
public static final String Package = "org.apache.tomcat.util.buf";
}

View File

@@ -0,0 +1,118 @@
/*
* 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.tomcat.util.buf;
import org.apache.tomcat.util.res.StringManager;
/**
* Tables useful when converting byte arrays to and from strings of hexadecimal
* digits.
* Code from Ajp11, from Apache's JServ.
*
* @author Craig R. McClanahan
*/
public final class HexUtils {
private static final StringManager sm =
StringManager.getManager(Constants.Package);
// -------------------------------------------------------------- Constants
/**
* Table for HEX to DEC byte translation.
*/
private static final int[] DEC = {
00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15,
};
/**
* Table for DEC to HEX byte translation.
*/
private static final byte[] HEX =
{ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
(byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
(byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
/**
* Table for byte to hex string translation.
*/
private static final char[] hex = "0123456789abcdef".toCharArray();
// --------------------------------------------------------- Static Methods
public static int getDec(int index) {
// Fast for correct values, slower for incorrect ones
try {
return DEC[index - '0'];
} catch (ArrayIndexOutOfBoundsException ex) {
return -1;
}
}
public static byte getHex(int index) {
return HEX[index];
}
public static String toHexString(byte[] bytes) {
if (null == bytes) {
return null;
}
StringBuilder sb = new StringBuilder(bytes.length << 1);
for(int i = 0; i < bytes.length; ++i) {
sb.append(hex[(bytes[i] & 0xf0) >> 4])
.append(hex[(bytes[i] & 0x0f)])
;
}
return sb.toString();
}
public static byte[] fromHexString(String input) {
if (input == null) {
return null;
}
if ((input.length() & 1) == 1) {
// Odd number of characters
throw new IllegalArgumentException(sm.getString("hexUtils.fromHex.oddDigits"));
}
char[] inputChars = input.toCharArray();
byte[] result = new byte[input.length() >> 1];
for (int i = 0; i < result.length; i++) {
int upperNibble = getDec(inputChars[2*i]);
int lowerNibble = getDec(inputChars[2*i + 1]);
if (upperNibble < 0 || lowerNibble < 0) {
// Non hex character
throw new IllegalArgumentException(sm.getString("hexUtils.fromHex.nonHex"));
}
result[i] = (byte) ((upperNibble << 4) + lowerNibble);
}
return result;
}
}

View File

@@ -0,0 +1,34 @@
# 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.
asn1Parser.lengthInvalid=Invalid length [{0}] bytes reported when the input data length is [{1}] bytes
asn1Parser.tagMismatch=Expected to find value [{0}] but found value [{1}]
b2cConverter.unknownEncoding=The character encoding [{0}] is not supported
byteBufferUtils.cleaner=Cannot use direct ByteBuffer cleaner, memory leaking may occur
c2bConverter.recycleFailed=Failed to recycle the C2B Converter. Creating new BufferedWriter, WriteConvertor and IntermediateOutputStream.
hexUtils.fromHex.nonHex=The input must consist only of hex digits
hexUtils.fromHex.oddDigits=The input must consist of an even number of hex digits
uDecoder.eof=End of file (EOF)
uDecoder.noSlash=The encoded slash character is not allowed
uDecoder.urlDecode.conversionError=Failed to decode [{0}] using character set [{1}]
uDecoder.urlDecode.missingDigit=Failed to decode [{0}] because the % character must be followed by two hexademical digits
uDecoder.urlDecode.uee=Unable to URL decode the specified input since the encoding [{0}] is not supported.
udecoder.urlDecode.iae=It is practical to %nn decode a byte array since how the %nn is encoded will vary by character set

View File

@@ -0,0 +1,16 @@
# 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.
uDecoder.convertHexDigit.notHex=[{0}] ist keine hexadezimale Ziffer

View File

@@ -0,0 +1,16 @@
# 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.
b2cConverter.unknownEncoding=La codificación de carácter [{0}] no está soportada

View File

@@ -0,0 +1,29 @@
# 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.
asn1Parser.lengthInvalid=Une longueur d''octets invalide [{0}] a été rapportée alors que la longueur de données en entrée est de [{1}] octets
asn1Parser.tagMismatch=La valeur [{0}] était attendue mais la valeur [{1}] a été rencontrée
b2cConverter.unknownEncoding=L''encodage de caractères [{0}] n''est pas supporté
byteBufferUtils.cleaner=Impossible d'utiliser le nettoyeur de ByteBuffers directs, une fuite de mémoire peut se produire
hexUtils.fromHex.nonHex=L'entrée doit être uniquement des chiffres héxadécimaux
hexUtils.fromHex.oddDigits=L'entrée doit contenir un nombre pair de chiffres héxadécimaux
uDecoder.eof=Fin de fichier (EOF)
uDecoder.noSlash=Un caractère slash encodé n'est pas autorisé
uDecoder.urlDecode.conversionError=Echec de décodage [{0}] en utilisant le jeu de caractères [{1}]
uDecoder.urlDecode.missingDigit=Impossible de décoder [{0}] parce que le caractère % doit être suivi de deux chiffres héxadécimaux

View File

@@ -0,0 +1,26 @@
# 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.
b2cConverter.unknownEncoding=文字エンコーディング [{0}] は未対応です。
byteBufferUtils.cleaner=直接ByteBufferクリーナーを使用することはできません、メモリリークが発生する可能性があります。
hexUtils.fromHex.nonHex=入力は16進数でなければなりません
hexUtils.fromHex.oddDigits=入力は、偶数の16進数で構成する必要があります。
uDecoder.eof=予期せぬ場所で終端に達しました。
uDecoder.noSlash="/" を符号化して含めることはできません。
uDecoder.urlDecode.conversionError=文字セット[{1}]を使用して[{0}]のデコードに失敗しました
uDecoder.urlDecode.missingDigit=文字の後ろに2つの16進数字が続く必要があるため、[{0}]のデコードに失敗しました。

View File

@@ -0,0 +1,29 @@
# 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.
asn1Parser.lengthInvalid=입력 데이터의 바이트 길이가 [{1}]인데, 유효하지 않은 바이트 길이 [{0}](이)가 보고되었습니다.
asn1Parser.tagMismatch=[{0}] 값이 기대 되었는데, [{1}] 값이 발견되었습니다.
b2cConverter.unknownEncoding=문자 인코딩 [{0}]은(는) 지원되지 않습니다.
byteBufferUtils.cleaner=직접적인 ByteBuffer cleaner를 사용할 수 없습니다. 메모리 누수가 발생할 수 있습니다.
hexUtils.fromHex.nonHex=입력은 오직 16진수 숫자로만 이루어져야 합니다.
hexUtils.fromHex.oddDigits=입력은 반드시 짝수 개의 16진수 숫자들로 이루어져야 합니다.
uDecoder.eof=파일의 끝 (EOF)
uDecoder.noSlash=인코딩된 슬래시 문자는 허용되지 않습니다.
uDecoder.urlDecode.conversionError=문자셋 [{1}]을(를) 사용하여 [{0}]을(를) 디코드하지 못했습니다.
uDecoder.urlDecode.missingDigit=% 문자 뒤에 두 개의 16진수 숫자들이 이어져야 하기 때문에, [{0}]을(를) 디코드하지 못했습니다.

View File

@@ -0,0 +1,21 @@
# 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.
asn1Parser.lengthInvalid=无效长度 [{0}]字节报告,但是输入数据的长度是 [{1}]字节
asn1Parser.tagMismatch=期望找到值 [{0}]但是却找到值 [{1}]
hexUtils.fromHex.nonHex=输入只能由十六进制数字组成
uDecoder.urlDecode.conversionError=使用编码[{1}]解码[{0}]失败

View File

@@ -0,0 +1,578 @@
/*
* 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.tomcat.util.buf;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
/**
* This class is used to represent a subarray of bytes in an HTTP message.
* It represents all request/response elements. The byte/char conversions are
* delayed and cached. Everything is recyclable.
*
* The object can represent a byte[], a char[], or a (sub) String. All
* operations can be made in case sensitive mode or not.
*
* @author dac@eng.sun.com
* @author James Todd [gonzo@eng.sun.com]
* @author Costin Manolache
*/
public final class MessageBytes implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
// primary type ( whatever is set as original value )
private int type = T_NULL;
public static final int T_NULL = 0;
/** getType() is T_STR if the the object used to create the MessageBytes
was a String */
public static final int T_STR = 1;
/** getType() is T_BYTES if the the object used to create the MessageBytes
was a byte[] */
public static final int T_BYTES = 2;
/** getType() is T_CHARS if the the object used to create the MessageBytes
was a char[] */
public static final int T_CHARS = 3;
private int hashCode=0;
// did we compute the hashcode ?
private boolean hasHashCode=false;
// Internal objects to represent array + offset, and specific methods
private final ByteChunk byteC=new ByteChunk();
private final CharChunk charC=new CharChunk();
// String
private String strValue;
// true if a String value was computed. Probably not needed,
// strValue!=null is the same
private boolean hasStrValue=false;
/**
* Creates a new, uninitialized MessageBytes object.
* Use static newInstance() in order to allow
* future hooks.
*/
private MessageBytes() {
}
/**
* Construct a new MessageBytes instance.
* @return the instance
*/
public static MessageBytes newInstance() {
return factory.newInstance();
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public boolean isNull() {
return byteC.isNull() && charC.isNull() && !hasStrValue;
}
/**
* Resets the message bytes to an uninitialized (NULL) state.
*/
public void recycle() {
type=T_NULL;
byteC.recycle();
charC.recycle();
strValue=null;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Sets the content to the specified subarray of bytes.
*
* @param b the bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
byteC.setBytes( b, off, len );
type=T_BYTES;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Sets the content to be a char[]
*
* @param c the chars
* @param off the start offset of the chars
* @param len the length of the chars
*/
public void setChars( char[] c, int off, int len ) {
charC.setChars( c, off, len );
type=T_CHARS;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Set the content to be a string
* @param s The string
*/
public void setString( String s ) {
strValue=s;
hasHashCode=false;
hasLongValue=false;
if (s == null) {
hasStrValue=false;
type=T_NULL;
} else {
hasStrValue=true;
type=T_STR;
}
}
// -------------------- Conversion and getters --------------------
/**
* Compute the string value.
* @return the string
*/
@Override
public String toString() {
if (hasStrValue) {
return strValue;
}
switch (type) {
case T_CHARS:
strValue = charC.toString();
hasStrValue = true;
return strValue;
case T_BYTES:
strValue = byteC.toString();
hasStrValue = true;
return strValue;
}
return null;
}
//----------------------------------------
/**
* Return the type of the original content. Can be
* T_STR, T_BYTES, T_CHARS or T_NULL
* @return the type
*/
public int getType() {
return type;
}
/**
* Returns the byte chunk, representing the byte[] and offset/length.
* Valid only if T_BYTES or after a conversion was made.
* @return the byte chunk
*/
public ByteChunk getByteChunk() {
return byteC;
}
/**
* Returns the char chunk, representing the char[] and offset/length.
* Valid only if T_CHARS or after a conversion was made.
* @return the char chunk
*/
public CharChunk getCharChunk() {
return charC;
}
/**
* Returns the string value.
* Valid only if T_STR or after a conversion was made.
* @return the string
*/
public String getString() {
return strValue;
}
/**
* @return the Charset used for string&lt;-&gt;byte conversions.
*/
public Charset getCharset() {
return byteC.getCharset();
}
/**
* Set the Charset used for string&lt;-&gt;byte conversions.
* @param charset The charset
*/
public void setCharset(Charset charset) {
byteC.setCharset(charset);
}
/**
* Do a char-&gt;byte conversion.
*/
public void toBytes() {
if (isNull()) {
return;
}
if (!byteC.isNull()) {
type = T_BYTES;
return;
}
toString();
type = T_BYTES;
Charset charset = byteC.getCharset();
ByteBuffer result = charset.encode(strValue);
byteC.setBytes(result.array(), result.arrayOffset(), result.limit());
}
/**
* Convert to char[] and fill the CharChunk.
* XXX Not optimized - it converts to String first.
*/
public void toChars() {
if (isNull()) {
return;
}
if (!charC.isNull()) {
type = T_CHARS;
return;
}
// inefficient
toString();
type = T_CHARS;
char cc[] = strValue.toCharArray();
charC.setChars(cc, 0, cc.length);
}
/**
* Returns the length of the original buffer.
* Note that the length in bytes may be different from the length
* in chars.
* @return the length
*/
public int getLength() {
if(type==T_BYTES) {
return byteC.getLength();
}
if(type==T_CHARS) {
return charC.getLength();
}
if(type==T_STR) {
return strValue.length();
}
toString();
if( strValue==null ) {
return 0;
}
return strValue.length();
}
// -------------------- equals --------------------
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return <code>true</code> if the comparison succeeded, <code>false</code> otherwise
*/
public boolean equals(String s) {
switch (type) {
case T_STR:
if (strValue == null) {
return s == null;
}
return strValue.equals( s );
case T_CHARS:
return charC.equals( s );
case T_BYTES:
return byteC.equals( s );
default:
return false;
}
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return <code>true</code> if the comparison succeeded, <code>false</code> otherwise
*/
public boolean equalsIgnoreCase(String s) {
switch (type) {
case T_STR:
if (strValue == null) {
return s == null;
}
return strValue.equalsIgnoreCase( s );
case T_CHARS:
return charC.equalsIgnoreCase( s );
case T_BYTES:
return byteC.equalsIgnoreCase( s );
default:
return false;
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MessageBytes) {
return equals((MessageBytes) obj);
}
return false;
}
public boolean equals(MessageBytes mb) {
switch (type) {
case T_STR:
return mb.equals( strValue );
}
if( mb.type != T_CHARS &&
mb.type!= T_BYTES ) {
// it's a string or int/date string value
return equals( mb.toString() );
}
// mb is either CHARS or BYTES.
// this is either CHARS or BYTES
// Deal with the 4 cases ( in fact 3, one is symmetric)
if( mb.type == T_CHARS && type==T_CHARS ) {
return charC.equals( mb.charC );
}
if( mb.type==T_BYTES && type== T_BYTES ) {
return byteC.equals( mb.byteC );
}
if( mb.type== T_CHARS && type== T_BYTES ) {
return byteC.equals( mb.charC );
}
if( mb.type== T_BYTES && type== T_CHARS ) {
return mb.byteC.equals( charC );
}
// can't happen
return true;
}
/**
* @return <code>true</code> if the message bytes starts with the specified string.
* @param s the string
* @param pos The start position
*/
public boolean startsWithIgnoreCase(String s, int pos) {
switch (type) {
case T_STR:
if( strValue==null ) {
return false;
}
if( strValue.length() < pos + s.length() ) {
return false;
}
for( int i=0; i<s.length(); i++ ) {
if( Ascii.toLower( s.charAt( i ) ) !=
Ascii.toLower( strValue.charAt( pos + i ))) {
return false;
}
}
return true;
case T_CHARS:
return charC.startsWithIgnoreCase( s, pos );
case T_BYTES:
return byteC.startsWithIgnoreCase( s, pos );
default:
return false;
}
}
// -------------------- Hash code --------------------
@Override
public int hashCode() {
if( hasHashCode ) {
return hashCode;
}
int code = 0;
code=hash();
hashCode=code;
hasHashCode=true;
return code;
}
// normal hash.
private int hash() {
int code=0;
switch (type) {
case T_STR:
// We need to use the same hash function
for (int i = 0; i < strValue.length(); i++) {
code = code * 37 + strValue.charAt( i );
}
return code;
case T_CHARS:
return charC.hash();
case T_BYTES:
return byteC.hash();
default:
return 0;
}
}
// Inefficient initial implementation. Will be replaced on the next
// round of tune-up
public int indexOf(String s, int starting) {
toString();
return strValue.indexOf( s, starting );
}
// Inefficient initial implementation. Will be replaced on the next
// round of tune-up
public int indexOf(String s) {
return indexOf( s, 0 );
}
public int indexOfIgnoreCase(String s, int starting) {
toString();
String upper=strValue.toUpperCase(Locale.ENGLISH);
String sU=s.toUpperCase(Locale.ENGLISH);
return upper.indexOf( sU, starting );
}
/**
* Copy the src into this MessageBytes, allocating more space if needed.
* @param src The source
* @throws IOException Writing overflow data to the output channel failed
*/
public void duplicate( MessageBytes src ) throws IOException
{
switch( src.getType() ) {
case MessageBytes.T_BYTES:
type=T_BYTES;
ByteChunk bc=src.getByteChunk();
byteC.allocate( 2 * bc.getLength(), -1 );
byteC.append( bc );
break;
case MessageBytes.T_CHARS:
type=T_CHARS;
CharChunk cc=src.getCharChunk();
charC.allocate( 2 * cc.getLength(), -1 );
charC.append( cc );
break;
case MessageBytes.T_STR:
type=T_STR;
String sc=src.getString();
this.setString( sc );
break;
}
setCharset(src.getCharset());
}
// -------------------- Deprecated code --------------------
// efficient long
// XXX used only for headers - shouldn't be stored here.
private long longValue;
private boolean hasLongValue=false;
/**
* Set the buffer to the representation of a long.
* @param l The long
*/
public void setLong(long l) {
byteC.allocate(32, 64);
long current = l;
byte[] buf = byteC.getBuffer();
int start = 0;
int end = 0;
if (l == 0) {
buf[end++] = (byte) '0';
}
if (l < 0) {
current = -l;
buf[end++] = (byte) '-';
}
while (current > 0) {
int digit = (int) (current % 10);
current = current / 10;
buf[end++] = HexUtils.getHex(digit);
}
byteC.setOffset(0);
byteC.setEnd(end);
// Inverting buffer
end--;
if (l < 0) {
start++;
}
while (end > start) {
byte temp = buf[start];
buf[start] = buf[end];
buf[end] = temp;
start++;
end--;
}
longValue=l;
hasStrValue=false;
hasHashCode=false;
hasLongValue=true;
type=T_BYTES;
}
// Used for headers conversion
/**
* Convert the buffer to an long, cache the value.
* @return the long value
*/
public long getLong() {
if( hasLongValue ) {
return longValue;
}
switch (type) {
case T_BYTES:
longValue=byteC.getLong();
break;
default:
longValue=Long.parseLong(toString());
}
hasLongValue=true;
return longValue;
}
// -------------------- Future may be different --------------------
private static final MessageBytesFactory factory=new MessageBytesFactory();
private static class MessageBytesFactory {
protected MessageBytesFactory() {
}
public MessageBytes newInstance() {
return new MessageBytes();
}
}
}

View File

@@ -0,0 +1,711 @@
/*
* 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.tomcat.util.buf;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* This class implements a String cache for ByteChunk and CharChunk.
*
* @author Remy Maucherat
*/
public class StringCache {
private static final Log log = LogFactory.getLog(StringCache.class);
// ------------------------------------------------------- Static Variables
/**
* Enabled ?
*/
protected static boolean byteEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.byte.enabled", "false")));
protected static boolean charEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.char.enabled", "false")));
protected static int trainThreshold = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.trainThreshold", "20000"));
protected static int cacheSize = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.cacheSize", "200"));
protected static final int maxStringSize =
Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.maxStringSize", "128"));
/**
* Statistics hash map for byte chunk.
*/
protected static final HashMap<ByteEntry,int[]> bcStats =
new HashMap<>(cacheSize);
/**
* toString count for byte chunk.
*/
protected static int bcCount = 0;
/**
* Cache for byte chunk.
*/
protected static volatile ByteEntry[] bcCache = null;
/**
* Statistics hash map for char chunk.
*/
protected static final HashMap<CharEntry,int[]> ccStats =
new HashMap<>(cacheSize);
/**
* toString count for char chunk.
*/
protected static int ccCount = 0;
/**
* Cache for char chunk.
*/
protected static volatile CharEntry[] ccCache = null;
/**
* Access count.
*/
protected static int accessCount = 0;
/**
* Hit count.
*/
protected static int hitCount = 0;
// ------------------------------------------------------------ Properties
/**
* @return Returns the cacheSize.
*/
public int getCacheSize() {
return cacheSize;
}
/**
* @param cacheSize The cacheSize to set.
*/
public void setCacheSize(int cacheSize) {
StringCache.cacheSize = cacheSize;
}
/**
* @return Returns the enabled.
*/
public boolean getByteEnabled() {
return byteEnabled;
}
/**
* @param byteEnabled The enabled to set.
*/
public void setByteEnabled(boolean byteEnabled) {
StringCache.byteEnabled = byteEnabled;
}
/**
* @return Returns the enabled.
*/
public boolean getCharEnabled() {
return charEnabled;
}
/**
* @param charEnabled The enabled to set.
*/
public void setCharEnabled(boolean charEnabled) {
StringCache.charEnabled = charEnabled;
}
/**
* @return Returns the trainThreshold.
*/
public int getTrainThreshold() {
return trainThreshold;
}
/**
* @param trainThreshold The trainThreshold to set.
*/
public void setTrainThreshold(int trainThreshold) {
StringCache.trainThreshold = trainThreshold;
}
/**
* @return Returns the accessCount.
*/
public int getAccessCount() {
return accessCount;
}
/**
* @return Returns the hitCount.
*/
public int getHitCount() {
return hitCount;
}
// -------------------------------------------------- Public Static Methods
public void reset() {
hitCount = 0;
accessCount = 0;
synchronized (bcStats) {
bcCache = null;
bcCount = 0;
}
synchronized (ccStats) {
ccCache = null;
ccCount = 0;
}
}
public static String toString(ByteChunk bc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (bcCache == null) {
String value = bc.toStringInternal();
if (byteEnabled && (value.length() < maxStringSize)) {
// If training, everything is synced
synchronized (bcStats) {
// If the cache has been generated on a previous invocation
// while waiting for the lock, just return the toString
// value we just calculated
if (bcCache != null) {
return value;
}
// Two cases: either we just exceeded the train count, in
// which case the cache must be created, or we just update
// the count for the string
if (bcCount > trainThreshold) {
long t1 = System.currentTimeMillis();
// Sort the entries according to occurrence
TreeMap<Integer,ArrayList<ByteEntry>> tempMap =
new TreeMap<>();
for (Entry<ByteEntry,int[]> item : bcStats.entrySet()) {
ByteEntry entry = item.getKey();
int[] countA = item.getValue();
Integer count = Integer.valueOf(countA[0]);
// Add to the list for that count
ArrayList<ByteEntry> list = tempMap.get(count);
if (list == null) {
// Create list
list = new ArrayList<>();
tempMap.put(count, list);
}
list.add(entry);
}
// Allocate array of the right size
int size = bcStats.size();
if (size > cacheSize) {
size = cacheSize;
}
ByteEntry[] tempbcCache = new ByteEntry[size];
// Fill it up using an alphabetical order
// and a dumb insert sort
ByteChunk tempChunk = new ByteChunk();
int n = 0;
while (n < size) {
Object key = tempMap.lastKey();
ArrayList<ByteEntry> list = tempMap.get(key);
for (int i = 0; i < list.size() && n < size; i++) {
ByteEntry entry = list.get(i);
tempChunk.setBytes(entry.name, 0,
entry.name.length);
int insertPos = findClosest(tempChunk,
tempbcCache, n);
if (insertPos == n) {
tempbcCache[n + 1] = entry;
} else {
System.arraycopy(tempbcCache, insertPos + 1,
tempbcCache, insertPos + 2,
n - insertPos - 1);
tempbcCache[insertPos + 1] = entry;
}
n++;
}
tempMap.remove(key);
}
bcCount = 0;
bcStats.clear();
bcCache = tempbcCache;
if (log.isDebugEnabled()) {
long t2 = System.currentTimeMillis();
log.debug("ByteCache generation time: " +
(t2 - t1) + "ms");
}
} else {
bcCount++;
// Allocate new ByteEntry for the lookup
ByteEntry entry = new ByteEntry();
entry.value = value;
int[] count = bcStats.get(entry);
if (count == null) {
int end = bc.getEnd();
int start = bc.getStart();
// Create byte array and copy bytes
entry.name = new byte[bc.getLength()];
System.arraycopy(bc.getBuffer(), start, entry.name,
0, end - start);
// Set encoding
entry.charset = bc.getCharset();
// Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
bcStats.put(entry, count);
} else {
count[0] = count[0] + 1;
}
}
}
}
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(bc);
if (result == null) {
return bc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
public static String toString(CharChunk cc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (ccCache == null) {
String value = cc.toStringInternal();
if (charEnabled && (value.length() < maxStringSize)) {
// If training, everything is synced
synchronized (ccStats) {
// If the cache has been generated on a previous invocation
// while waiting for the lock, just return the toString
// value we just calculated
if (ccCache != null) {
return value;
}
// Two cases: either we just exceeded the train count, in
// which case the cache must be created, or we just update
// the count for the string
if (ccCount > trainThreshold) {
long t1 = System.currentTimeMillis();
// Sort the entries according to occurrence
TreeMap<Integer,ArrayList<CharEntry>> tempMap =
new TreeMap<>();
for (Entry<CharEntry,int[]> item : ccStats.entrySet()) {
CharEntry entry = item.getKey();
int[] countA = item.getValue();
Integer count = Integer.valueOf(countA[0]);
// Add to the list for that count
ArrayList<CharEntry> list = tempMap.get(count);
if (list == null) {
// Create list
list = new ArrayList<>();
tempMap.put(count, list);
}
list.add(entry);
}
// Allocate array of the right size
int size = ccStats.size();
if (size > cacheSize) {
size = cacheSize;
}
CharEntry[] tempccCache = new CharEntry[size];
// Fill it up using an alphabetical order
// and a dumb insert sort
CharChunk tempChunk = new CharChunk();
int n = 0;
while (n < size) {
Object key = tempMap.lastKey();
ArrayList<CharEntry> list = tempMap.get(key);
for (int i = 0; i < list.size() && n < size; i++) {
CharEntry entry = list.get(i);
tempChunk.setChars(entry.name, 0,
entry.name.length);
int insertPos = findClosest(tempChunk,
tempccCache, n);
if (insertPos == n) {
tempccCache[n + 1] = entry;
} else {
System.arraycopy(tempccCache, insertPos + 1,
tempccCache, insertPos + 2,
n - insertPos - 1);
tempccCache[insertPos + 1] = entry;
}
n++;
}
tempMap.remove(key);
}
ccCount = 0;
ccStats.clear();
ccCache = tempccCache;
if (log.isDebugEnabled()) {
long t2 = System.currentTimeMillis();
log.debug("CharCache generation time: " +
(t2 - t1) + "ms");
}
} else {
ccCount++;
// Allocate new CharEntry for the lookup
CharEntry entry = new CharEntry();
entry.value = value;
int[] count = ccStats.get(entry);
if (count == null) {
int end = cc.getEnd();
int start = cc.getStart();
// Create char array and copy chars
entry.name = new char[cc.getLength()];
System.arraycopy(cc.getBuffer(), start, entry.name,
0, end - start);
// Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
ccStats.put(entry, count);
} else {
count[0] = count[0] + 1;
}
}
}
}
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(cc);
if (result == null) {
return cc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
// ----------------------------------------------------- Protected Methods
/**
* Compare given byte chunk with byte array.
* @param name The name to compare
* @param compareTo The compared to data
* @return -1, 0 or +1 if inferior, equal, or superior to the String.
*/
protected static final int compare(ByteChunk name, byte[] compareTo) {
int result = 0;
byte[] b = name.getBuffer();
int start = name.getStart();
int end = name.getEnd();
int len = compareTo.length;
if ((end - start) < len) {
len = end - start;
}
for (int i = 0; (i < len) && (result == 0); i++) {
if (b[i + start] > compareTo[i]) {
result = 1;
} else if (b[i + start] < compareTo[i]) {
result = -1;
}
}
if (result == 0) {
if (compareTo.length > (end - start)) {
result = -1;
} else if (compareTo.length < (end - start)) {
result = 1;
}
}
return result;
}
/**
* Find an entry given its name in the cache and return the associated
* String.
* @param name The name to find
* @return the corresponding value
*/
protected static final String find(ByteChunk name) {
int pos = findClosest(name, bcCache, bcCache.length);
if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
|| !(name.getCharset().equals(bcCache[pos].charset))) {
return null;
} else {
return bcCache[pos].value;
}
}
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
* given array.
* @param name The name to find
* @param array The array in which to look
* @param len The effective length of the array
* @return the position of the best match
*/
protected static final int findClosest(ByteChunk name, ByteEntry[] array,
int len) {
int a = 0;
int b = len - 1;
// Special cases: -1 and 0
if (b == -1) {
return -1;
}
if (compare(name, array[0].name) < 0) {
return -1;
}
if (b == 0) {
return 0;
}
int i = 0;
while (true) {
i = (b + a) >>> 1;
int result = compare(name, array[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, array[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
/**
* Compare given char chunk with char array.
* @param name The name to compare
* @param compareTo The compared to data
* @return -1, 0 or +1 if inferior, equal, or superior to the String.
*/
protected static final int compare(CharChunk name, char[] compareTo) {
int result = 0;
char[] c = name.getBuffer();
int start = name.getStart();
int end = name.getEnd();
int len = compareTo.length;
if ((end - start) < len) {
len = end - start;
}
for (int i = 0; (i < len) && (result == 0); i++) {
if (c[i + start] > compareTo[i]) {
result = 1;
} else if (c[i + start] < compareTo[i]) {
result = -1;
}
}
if (result == 0) {
if (compareTo.length > (end - start)) {
result = -1;
} else if (compareTo.length < (end - start)) {
result = 1;
}
}
return result;
}
/**
* Find an entry given its name in the cache and return the associated
* String.
* @param name The name to find
* @return the corresponding value
*/
protected static final String find(CharChunk name) {
int pos = findClosest(name, ccCache, ccCache.length);
if ((pos < 0) || (compare(name, ccCache[pos].name) != 0)) {
return null;
} else {
return ccCache[pos].value;
}
}
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
* given array.
* @param name The name to find
* @param array The array in which to look
* @param len The effective length of the array
* @return the position of the best match
*/
protected static final int findClosest(CharChunk name, CharEntry[] array,
int len) {
int a = 0;
int b = len - 1;
// Special cases: -1 and 0
if (b == -1) {
return -1;
}
if (compare(name, array[0].name) < 0 ) {
return -1;
}
if (b == 0) {
return 0;
}
int i = 0;
while (true) {
i = (b + a) >>> 1;
int result = compare(name, array[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, array[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
// -------------------------------------------------- ByteEntry Inner Class
private static class ByteEntry {
private byte[] name = null;
private Charset charset = null;
private String value = null;
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ByteEntry) {
return value.equals(((ByteEntry) obj).value);
}
return false;
}
}
// -------------------------------------------------- CharEntry Inner Class
private static class CharEntry {
private char[] name = null;
private String value = null;
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CharEntry) {
return value.equals(((CharEntry) obj).value);
}
return false;
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.tomcat.util.buf;
import java.util.Arrays;
import java.util.Collection;
/**
* Utility methods to build a separated list from a given set (not
* java.util.Set) of inputs and return that list as a string or append it to an
* existing StringBuilder. If the given set is null or empty, an empty string
* will be returned.
*/
public final class StringUtils {
private static final String EMPTY_STRING = "";
private StringUtils() {
// Utility class
}
public static String join(String[] array) {
if (array == null) {
return EMPTY_STRING;
}
return join(Arrays.asList(array));
}
public static void join(String[] array, char separator, StringBuilder sb) {
if (array == null) {
return;
}
join(Arrays.asList(array), separator, sb);
}
public static String join(Collection<String> collection) {
return join(collection, ',');
}
public static String join(Collection<String> collection, char separator) {
// Shortcut
if (collection == null || collection.isEmpty()) {
return EMPTY_STRING;
}
StringBuilder result = new StringBuilder();
join(collection, separator, result);
return result.toString();
}
public static void join(Iterable<String> iterable, char separator, StringBuilder sb) {
join(iterable, separator,
new Function<String>() {@Override public String apply(String t) { return t; }}, sb);
}
public static <T> void join(T[] array, char separator, Function<T> function,
StringBuilder sb) {
if (array == null) {
return;
}
join(Arrays.asList(array), separator, function, sb);
}
public static <T> void join(Iterable<T> iterable, char separator, Function<T> function,
StringBuilder sb) {
if (iterable == null) {
return;
}
boolean first = true;
for (T value : iterable) {
if (first) {
first = false;
} else {
sb.append(separator);
}
sb.append(function.apply(value));
}
}
public interface Function<T> {
public String apply(T t);
}
}

View File

@@ -0,0 +1,495 @@
/*
* 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.tomcat.util.buf;
import java.io.ByteArrayOutputStream;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
/**
* All URL decoding happens here. This way we can reuse, review, optimize
* without adding complexity to the buffers.
*
* The conversion will modify the original buffer.
*
* @author Costin Manolache
*/
public final class UDecoder {
private static final StringManager sm = StringManager.getManager(UDecoder.class);
private static final Log log = LogFactory.getLog(UDecoder.class);
public static final boolean ALLOW_ENCODED_SLASH =
Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"));
private static class DecodeException extends CharConversionException {
private static final long serialVersionUID = 1L;
public DecodeException(String s) {
super(s);
}
@Override
public synchronized Throwable fillInStackTrace() {
// This class does not provide a stack trace
return this;
}
}
/** Unexpected end of data. */
private static final IOException EXCEPTION_EOF = new DecodeException(sm.getString("uDecoder.eof"));
/** %xx with not-hex digit */
private static final IOException EXCEPTION_NOT_HEX_DIGIT = new DecodeException(
"isHexDigit");
/** %-encoded slash is forbidden in resource path */
private static final IOException EXCEPTION_SLASH = new DecodeException(
"noSlash");
public UDecoder()
{
}
/**
* URLDecode, will modify the source.
* @param mb The URL encoded bytes
* @param query <code>true</code> if this is a query string
* @throws IOException Invalid %xx URL encoding
*/
public void convert( ByteChunk mb, boolean query )
throws IOException
{
int start=mb.getOffset();
byte buff[]=mb.getBytes();
int end=mb.getEnd();
int idx= ByteChunk.findByte( buff, start, end, (byte) '%' );
int idx2=-1;
if( query ) {
idx2= ByteChunk.findByte( buff, start, (idx >= 0 ? idx : end), (byte) '+' );
}
if( idx<0 && idx2<0 ) {
return;
}
// idx will be the smallest positive index ( first % or + )
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
idx=idx2;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
for( int j=idx; j<end; j++, idx++ ) {
if( buff[ j ] == '+' && query) {
buff[idx]= (byte)' ' ;
} else if( buff[ j ] != '%' ) {
buff[idx]= buff[j];
} else {
// read next 2 digits
if( j+2 >= end ) {
throw EXCEPTION_EOF;
}
byte b1= buff[j+1];
byte b2=buff[j+2];
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw EXCEPTION_NOT_HEX_DIGIT;
}
j+=2;
int res=x2c( b1, b2 );
if (noSlash && (res == '/')) {
throw EXCEPTION_SLASH;
}
buff[idx]=(byte)res;
}
}
mb.setEnd( idx );
}
// -------------------- Additional methods --------------------
// XXX What do we do about charset ????
/**
* In-buffer processing - the buffer will be modified.
* @param mb The URL encoded chars
* @param query <code>true</code> if this is a query string
* @throws IOException Invalid %xx URL encoding
*/
public void convert( CharChunk mb, boolean query )
throws IOException
{
// log( "Converting a char chunk ");
int start=mb.getOffset();
char buff[]=mb.getBuffer();
int cend=mb.getEnd();
int idx= CharChunk.indexOf( buff, start, cend, '%' );
int idx2=-1;
if( query ) {
idx2= CharChunk.indexOf( buff, start, (idx >= 0 ? idx : cend), '+' );
}
if( idx<0 && idx2<0 ) {
return;
}
// idx will be the smallest positive index ( first % or + )
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
idx=idx2;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
for( int j=idx; j<cend; j++, idx++ ) {
if( buff[ j ] == '+' && query ) {
buff[idx]=( ' ' );
} else if( buff[ j ] != '%' ) {
buff[idx]=buff[j];
} else {
// read next 2 digits
if( j+2 >= cend ) {
// invalid
throw EXCEPTION_EOF;
}
char b1= buff[j+1];
char b2=buff[j+2];
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw EXCEPTION_NOT_HEX_DIGIT;
}
j+=2;
int res=x2c( b1, b2 );
if (noSlash && (res == '/')) {
throw EXCEPTION_SLASH;
}
buff[idx]=(char)res;
}
}
mb.setEnd( idx );
}
/**
* URLDecode, will modify the source
* @param mb The URL encoded String, bytes or chars
* @param query <code>true</code> if this is a query string
* @throws IOException Invalid %xx URL encoding
*/
public void convert(MessageBytes mb, boolean query)
throws IOException
{
switch (mb.getType()) {
case MessageBytes.T_STR:
String strValue=mb.toString();
if( strValue==null ) {
return;
}
try {
mb.setString( convert( strValue, query ));
} catch (RuntimeException ex) {
throw new DecodeException(ex.getMessage());
}
break;
case MessageBytes.T_CHARS:
CharChunk charC=mb.getCharChunk();
convert( charC, query );
break;
case MessageBytes.T_BYTES:
ByteChunk bytesC=mb.getByteChunk();
convert( bytesC, query );
break;
}
}
/**
* %xx decoding of a string. FIXME: this is inefficient.
* @param str The URL encoded string
* @param query <code>true</code> if this is a query string
* @return the decoded string
*/
public final String convert(String str, boolean query)
{
if (str == null) {
return null;
}
if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 ) {
return str;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
StringBuilder dec = new StringBuilder(); // decoded string output
int strPos = 0;
int strLen = str.length();
dec.ensureCapacity(str.length());
while (strPos < strLen) {
int laPos; // lookahead position
// look ahead to next URLencoded metacharacter, if any
for (laPos = strPos; laPos < strLen; laPos++) {
char laChar = str.charAt(laPos);
if ((laChar == '+' && query) || (laChar == '%')) {
break;
}
}
// if there were non-metacharacters, copy them all as a block
if (laPos > strPos) {
dec.append(str.substring(strPos,laPos));
strPos = laPos;
}
// shortcut out of here if we're at the end of the string
if (strPos >= strLen) {
break;
}
// process next metacharacter
char metaChar = str.charAt(strPos);
if (metaChar == '+') {
dec.append(' ');
strPos++;
continue;
} else if (metaChar == '%') {
// We throw the original exception - the super will deal with
// it
// try {
char res = (char) Integer.parseInt(
str.substring(strPos + 1, strPos + 3), 16);
if (noSlash && (res == '/')) {
throw new IllegalArgumentException(sm.getString("uDecoder.noSlash"));
}
dec.append(res);
strPos += 3;
}
}
return dec.toString();
}
/**
* Decode and return the specified URL-encoded String.
* When the byte array is converted to a string, ISO-885901 is used. This
* may be different than some other servers. It is assumed the string is not
* a query string.
*
* @param str The url-encoded string
* @return the decoded string
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str) {
return URLDecode(str, StandardCharsets.ISO_8859_1);
}
/**
* Decode and return the specified URL-encoded String. It is assumed the
* string is not a query string.
*
* @param str The url-encoded string
* @param enc The encoding to use; if null, ISO-885901 is used. If
* an unsupported encoding is specified null will be returned
* @return the decoded string
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*
* @deprecated This method will be removed in Tomcat 9
*/
@Deprecated
public static String URLDecode(String str, String enc) {
return URLDecode(str, enc, false);
}
/**
* Decode and return the specified URL-encoded String. It is assumed the
* string is not a query string.
*
* @param str The url-encoded string
* @param charset The character encoding to use; if null, ISO-8859-1 is
* used.
* @return the decoded string
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str, Charset charset) {
return URLDecode(str, charset, false);
}
/**
* Decode and return the specified URL-encoded String.
*
* @param str The url-encoded string
* @param enc The encoding to use; if null, ISO-8859-1 is used. If
* an unsupported encoding is specified null will be returned
* @param isQuery Is this a query string being processed
* @return the decoded string
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*
* @deprecated This method will be removed in Tomcat 9
*/
@Deprecated
public static String URLDecode(String str, String enc, boolean isQuery) {
Charset charset = null;
if (enc != null) {
try {
charset = B2CConverter.getCharset(enc);
} catch (UnsupportedEncodingException uee) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("uDecoder.urlDecode.uee", enc), uee);
}
}
}
return URLDecode(str, charset, isQuery);
}
/**
* Decode and return the specified URL-encoded byte array.
*
* @param bytes The url-encoded byte array
* @param enc The encoding to use; if null, ISO-8859-1 is used. If
* an unsupported encoding is specified null will be returned
* @param isQuery Is this a query string being processed
* @return the decoded string
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*
* @deprecated This method will be removed in Tomcat 9
*/
@Deprecated
public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
throw new IllegalArgumentException(sm.getString("udecoder.urlDecode.iae"));
}
private static String URLDecode(String str, Charset charset, boolean isQuery) {
if (str == null) {
return null;
}
if (str.indexOf('%') == -1) {
// No %nn sequences, so return string unchanged
return str;
}
if (charset == null) {
charset = StandardCharsets.ISO_8859_1;
}
/*
* Decoding is required.
*
* Potential complications:
* - The source String may be partially decoded so it is not valid to
* assume that the source String is ASCII.
* - Have to process as characters since there is no guarantee that the
* byte sequence for '%' is going to be the same in all character
* sets.
* - We don't know how many '%nn' sequences are required for a single
* character. It varies between character sets and some use a variable
* length.
*/
// This isn't perfect but it is a reasonable guess for the size of the
// array required
ByteArrayOutputStream baos = new ByteArrayOutputStream(str.length() * 2);
OutputStreamWriter osw = new OutputStreamWriter(baos, charset);
char[] sourceChars = str.toCharArray();
int len = sourceChars.length;
int ix = 0;
try {
while (ix < len) {
char c = sourceChars[ix++];
if (c == '%') {
osw.flush();
if (ix + 2 > len) {
throw new IllegalArgumentException(
sm.getString("uDecoder.urlDecode.missingDigit", str));
}
char c1 = sourceChars[ix++];
char c2 = sourceChars[ix++];
if (isHexDigit(c1) && isHexDigit(c2)) {
baos.write(x2c(c1, c2));
} else {
throw new IllegalArgumentException(
sm.getString("uDecoder.urlDecode.missingDigit", str));
}
} else if (c == '+' && isQuery) {
osw.append(' ');
} else {
osw.append(c);
}
}
osw.flush();
return baos.toString(charset.name());
} catch (IOException ioe) {
throw new IllegalArgumentException(
sm.getString("uDecoder.urlDecode.conversionError", str, charset.name()), ioe);
}
}
private static boolean isHexDigit( int c ) {
return ( ( c>='0' && c<='9' ) ||
( c>='a' && c<='f' ) ||
( c>='A' && c<='F' ));
}
private static int x2c( byte b1, byte b2 ) {
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
(b1 -'0');
digit*=16;
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
(b2 -'0');
return digit;
}
private static int x2c( char b1, char b2 ) {
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
(b1 -'0');
digit*=16;
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
(b2 -'0');
return digit;
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.tomcat.util.buf;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
/**
* Efficient implementation of an UTF-8 encoder.
* This class is not thread safe - you need one encoder per thread.
* The encoder will save and recycle the internal objects, avoiding
* garbage.
*
* You can add extra characters that you want preserved, for example
* while encoding a URL you can add "/".
*
* @author Costin Manolache
*/
public final class UEncoder {
public enum SafeCharsSet {
WITH_SLASH("/"), DEFAULT("");
private final BitSet safeChars;
private BitSet getSafeChars() {
return this.safeChars;
}
private SafeCharsSet(String additionalSafeChars) {
safeChars = initialSafeChars();
for (char c : additionalSafeChars.toCharArray()) {
safeChars.set(c);
}
}
}
// Not static - the set may differ ( it's better than adding
// an extra check for "/", "+", etc
private BitSet safeChars=null;
private C2BConverter c2b=null;
private ByteChunk bb=null;
private CharChunk cb=null;
private CharChunk output=null;
/**
* Create a UEncoder with an unmodifiable safe character set.
*
* @param safeCharsSet safe characters for this encoder
*/
public UEncoder(SafeCharsSet safeCharsSet) {
this.safeChars = safeCharsSet.getSafeChars();
}
/**
* URL Encode string, using a specified encoding.
*
* @param s string to be encoded
* @param start the beginning index, inclusive
* @param end the ending index, exclusive
*
* @return A new CharChunk contained the URL encoded string
*
* @throws IOException If an I/O error occurs
*/
public CharChunk encodeURL(String s, int start, int end)
throws IOException {
if (c2b == null) {
bb = new ByteChunk(8); // small enough.
cb = new CharChunk(2); // small enough.
output = new CharChunk(64); // small enough.
c2b = new C2BConverter(StandardCharsets.UTF_8);
} else {
bb.recycle();
cb.recycle();
output.recycle();
}
for (int i = start; i < end; i++) {
char c = s.charAt(i);
if (safeChars.get(c)) {
output.append(c);
} else {
cb.append(c);
c2b.convert(cb, bb);
// "surrogate" - UTF is _not_ 16 bit, but 21 !!!!
// ( while UCS is 31 ). Amazing...
if (c >= 0xD800 && c <= 0xDBFF) {
if ((i+1) < end) {
char d = s.charAt(i+1);
if (d >= 0xDC00 && d <= 0xDFFF) {
cb.append(d);
c2b.convert(cb, bb);
i++;
}
}
}
urlEncode(output, bb);
cb.recycle();
bb.recycle();
}
}
return output;
}
protected void urlEncode(CharChunk out, ByteChunk bb)
throws IOException {
byte[] bytes = bb.getBuffer();
for (int j = bb.getStart(); j < bb.getEnd(); j++) {
out.append('%');
char ch = Character.forDigit((bytes[j] >> 4) & 0xF, 16);
out.append(ch);
ch = Character.forDigit(bytes[j] & 0xF, 16);
out.append(ch);
}
}
// -------------------- Internal implementation --------------------
private static BitSet initialSafeChars() {
BitSet initialSafeChars=new BitSet(128);
int i;
for (i = 'a'; i <= 'z'; i++) {
initialSafeChars.set(i);
}
for (i = 'A'; i <= 'Z'; i++) {
initialSafeChars.set(i);
}
for (i = '0'; i <= '9'; i++) {
initialSafeChars.set(i);
}
//safe
initialSafeChars.set('$');
initialSafeChars.set('-');
initialSafeChars.set('_');
initialSafeChars.set('.');
// Dangerous: someone may treat this as " "
// RFC1738 does allow it, it's not reserved
// initialSafeChars.set('+');
//extra
initialSafeChars.set('!');
initialSafeChars.set('*');
initialSafeChars.set('\'');
initialSafeChars.set('(');
initialSafeChars.set(')');
initialSafeChars.set(',');
return initialSafeChars;
}
}

View File

@@ -0,0 +1,197 @@
/*
* 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.tomcat.util.buf;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Pattern;
/**
* Utility class for working with URIs and URLs.
*/
public final class UriUtil {
private static final char[] HEX =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private static final Pattern PATTERN_EXCLAMATION_MARK = Pattern.compile("!/");
private static final Pattern PATTERN_CARET = Pattern.compile("\\^/");
private static final Pattern PATTERN_ASTERISK = Pattern.compile("\\*/");
private static final Pattern PATTERN_CUSTOM;
private static final String REPLACE_CUSTOM;
private static final String WAR_SEPARATOR;
static {
String custom = System.getProperty("org.apache.tomcat.util.buf.UriUtil.WAR_SEPARATOR");
if (custom == null) {
WAR_SEPARATOR = "*/";
PATTERN_CUSTOM = null;
REPLACE_CUSTOM = null;
} else {
WAR_SEPARATOR = custom + "/";
PATTERN_CUSTOM = Pattern.compile(Pattern.quote(WAR_SEPARATOR));
StringBuffer sb = new StringBuffer(custom.length() * 3);
// Deliberately use the platform's default encoding
byte[] ba = custom.getBytes();
for (int j = 0; j < ba.length; j++) {
// Converting each byte in the buffer
byte toEncode = ba[j];
sb.append('%');
int low = toEncode & 0x0f;
int high = (toEncode & 0xf0) >> 4;
sb.append(HEX[high]);
sb.append(HEX[low]);
}
REPLACE_CUSTOM = sb.toString();
}
}
private UriUtil() {
// Utility class. Hide default constructor
}
/**
* Determine if the character is allowed in the scheme of a URI.
* See RFC 2396, Section 3.1
*
* @param c The character to test
*
* @return {@code true} if a the character is allowed, otherwise {code
* @false}
*/
private static boolean isSchemeChar(char c) {
return Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.';
}
/**
* Determine if a URI string has a <code>scheme</code> component.
*
* @param uri The URI to test
*
* @return {@code true} if a scheme is present, otherwise {code @false}
*/
public static boolean hasScheme(CharSequence uri) {
int len = uri.length();
for(int i=0; i < len ; i++) {
char c = uri.charAt(i);
if(c == ':') {
return i > 0;
} else if(!UriUtil.isSchemeChar(c)) {
return false;
}
}
return false;
}
public static URL buildJarUrl(File jarFile) throws MalformedURLException {
return buildJarUrl(jarFile, null);
}
public static URL buildJarUrl(File jarFile, String entryPath) throws MalformedURLException {
return buildJarUrl(jarFile.toURI().toString(), entryPath);
}
public static URL buildJarUrl(String fileUrlString) throws MalformedURLException {
return buildJarUrl(fileUrlString, null);
}
public static URL buildJarUrl(String fileUrlString, String entryPath) throws MalformedURLException {
String safeString = makeSafeForJarUrl(fileUrlString);
StringBuilder sb = new StringBuilder();
sb.append(safeString);
sb.append("!/");
if (entryPath != null) {
sb.append(makeSafeForJarUrl(entryPath));
}
return new URL("jar", null, -1, sb.toString());
}
public static URL buildJarSafeUrl(File file) throws MalformedURLException {
String safe = makeSafeForJarUrl(file.toURI().toString());
return new URL(safe);
}
/*
* When testing on markt's desktop each iteration was taking ~1420ns when
* using String.replaceAll().
*
* Switching the implementation to use pre-compiled patterns and
* Pattern.matcher(input).replaceAll(replacement) reduced this by ~10%.
*
* Note: Given the very small absolute time of a single iteration, even for
* a web application with 1000 JARs this is only going to add ~3ms.
* It is therefore unlikely that further optimisation will be
* necessary.
*/
/*
* Pulled out into a separate method in case we need to handle other unusual
* sequences in the future.
*/
private static String makeSafeForJarUrl(String input) {
// Since "!/" has a special meaning in a JAR URL, make sure that the
// sequence is properly escaped if present.
String tmp = PATTERN_EXCLAMATION_MARK.matcher(input).replaceAll("%21/");
// Tomcat's custom jar:war: URL handling treats */ and ^/ as special
tmp = PATTERN_CARET.matcher(tmp).replaceAll("%5e/");
tmp = PATTERN_ASTERISK.matcher(tmp).replaceAll("%2a/");
if (PATTERN_CUSTOM != null) {
tmp = PATTERN_CUSTOM.matcher(tmp).replaceAll(REPLACE_CUSTOM);
}
return tmp;
}
/**
* Convert a URL of the form <code>war:file:...</code> to
* <code>jar:file:...</code>.
*
* @param warUrl The WAR URL to convert
*
* @return The equivalent JAR URL
*
* @throws MalformedURLException If the conversion fails
*/
public static URL warToJar(URL warUrl) throws MalformedURLException {
// Assumes that the spec is absolute and starts war:file:/...
String file = warUrl.getFile();
if (file.contains("*/")) {
file = file.replaceFirst("\\*/", "!/");
} else if (file.contains("^/")) {
file = file.replaceFirst("\\^/", "!/");
} else if (PATTERN_CUSTOM != null) {
file = file.replaceFirst(PATTERN_CUSTOM.pattern(), "!/");
}
return new URL("jar", warUrl.getHost(), warUrl.getPort(), file);
}
public static String getWarSeparator() {
return WAR_SEPARATOR;
}
}

View File

@@ -0,0 +1,299 @@
/*
* 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.tomcat.util.buf;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
/**
* Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
* code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
* does not reject these. It has also been modified to reject code points
* greater than U+10FFFF which the standard Java decoder rejects but the harmony
* one does not.
*/
public class Utf8Decoder extends CharsetDecoder {
// The next table contains information about UTF-8 charset and
// correspondence of 1st byte to the length of sequence
// For information please visit http://www.ietf.org/rfc/rfc3629.txt
//
// Please note, o means 0, actually.
// -------------------------------------------------------------------
// 0 1 2 3 Value
// -------------------------------------------------------------------
// oxxxxxxx 00000000 00000000 0xxxxxxx
// 11oyyyyy 1oxxxxxx 00000000 00000yyy yyxxxxxx
// 111ozzzz 1oyyyyyy 1oxxxxxx 00000000 zzzzyyyy yyxxxxxx
// 1111ouuu 1ouuzzzz 1oyyyyyy 1oxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
private static final int remainingBytes[] = {
// 1owwwwww
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// 11oyyyyy
-1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// 111ozzzz
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
// 1111ouuu
3, 3, 3, 3, 3, -1, -1, -1,
// > 11110111
-1, -1, -1, -1, -1, -1, -1, -1};
private static final int remainingNumbers[] = {0, // 0 1 2 3
4224, // (01o00000b << 6)+(1o000000b)
401536, // (011o0000b << 12)+(1o000000b << 6)+(1o000000b)
29892736 // (0111o000b << 18)+(1o000000b << 12)+(1o000000b <<
// 6)+(1o000000b)
};
private static final int lowerEncodingLimit[] = {-1, 0x80, 0x800, 0x10000};
public Utf8Decoder() {
super(StandardCharsets.UTF_8, 1.0f, 1.0f);
}
@Override
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
if (in.hasArray() && out.hasArray()) {
return decodeHasArray(in, out);
}
return decodeNotHasArray(in, out);
}
private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
try {
while (pos < limit) {
if (outRemaining == 0) {
return CoderResult.OVERFLOW;
}
int jchar = in.get();
if (jchar < 0) {
jchar = jchar & 0x7F;
int tail = remainingBytes[jchar];
if (tail == -1) {
return CoderResult.malformedForLength(1);
}
if (limit - pos < 1 + tail) {
// No early test for invalid sequences here as peeking
// at the next byte is harder
return CoderResult.UNDERFLOW;
}
int nextByte;
for (int i = 0; i < tail; i++) {
nextByte = in.get() & 0xFF;
if ((nextByte & 0xC0) != 0x80) {
return CoderResult.malformedForLength(1 + i);
}
jchar = (jchar << 6) + nextByte;
}
jchar -= remainingNumbers[tail];
if (jchar < lowerEncodingLimit[tail]) {
// Should have been encoded in a fewer octets
return CoderResult.malformedForLength(1);
}
pos += tail;
}
// Apache Tomcat added test
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
return CoderResult.unmappableForLength(3);
}
// Apache Tomcat added test
if (jchar > 0x10FFFF) {
return CoderResult.unmappableForLength(4);
}
if (jchar <= 0xffff) {
out.put((char) jchar);
outRemaining--;
} else {
if (outRemaining < 2) {
return CoderResult.OVERFLOW;
}
out.put((char) ((jchar >> 0xA) + 0xD7C0));
out.put((char) ((jchar & 0x3FF) + 0xDC00));
outRemaining -= 2;
}
pos++;
}
return CoderResult.UNDERFLOW;
} finally {
in.position(pos);
}
}
private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
final byte[] bArr = in.array();
final char[] cArr = out.array();
final int inIndexLimit = limit + in.arrayOffset();
int inIndex = pos + in.arrayOffset();
int outIndex = out.position() + out.arrayOffset();
// if someone would change the limit in process,
// he would face consequences
for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
int jchar = bArr[inIndex];
if (jchar < 0) {
jchar = jchar & 0x7F;
// If first byte is invalid, tail will be set to -1
int tail = remainingBytes[jchar];
if (tail == -1) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// Additional checks to detect invalid sequences ASAP
// Checks derived from Unicode 6.2, Chapter 3, Table 3-7
// Check 2nd byte
int tailAvailable = inIndexLimit - inIndex - 1;
if (tailAvailable > 0) {
// First byte C2..DF, second byte 80..BF
if (jchar > 0x41 && jchar < 0x60 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte E0, second byte A0..BF
if (jchar == 0x60 && (bArr[inIndex + 1] & 0xE0) != 0xA0) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte E1..EC, second byte 80..BF
if (jchar > 0x60 && jchar < 0x6D &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte ED, second byte 80..9F
if (jchar == 0x6D && (bArr[inIndex + 1] & 0xE0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte EE..EF, second byte 80..BF
if (jchar > 0x6D && jchar < 0x70 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F0, second byte 90..BF
if (jchar == 0x70 &&
((bArr[inIndex + 1] & 0xFF) < 0x90 ||
(bArr[inIndex + 1] & 0xFF) > 0xBF)) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F1..F3, second byte 80..BF
if (jchar > 0x70 && jchar < 0x74 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F4, second byte 80..8F
if (jchar == 0x74 &&
(bArr[inIndex + 1] & 0xF0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
}
// Check third byte if present and expected
if (tailAvailable > 1 && tail > 1) {
if ((bArr[inIndex + 2] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(2);
}
}
// Check fourth byte if present and expected
if (tailAvailable > 2 && tail > 2) {
if ((bArr[inIndex + 3] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(3);
}
}
if (tailAvailable < tail) {
break;
}
for (int i = 0; i < tail; i++) {
int nextByte = bArr[inIndex + i + 1] & 0xFF;
if ((nextByte & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1 + i);
}
jchar = (jchar << 6) + nextByte;
}
jchar -= remainingNumbers[tail];
if (jchar < lowerEncodingLimit[tail]) {
// Should have been encoded in fewer octets
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
inIndex += tail;
}
// Apache Tomcat added test
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
return CoderResult.unmappableForLength(3);
}
// Apache Tomcat added test
if (jchar > 0x10FFFF) {
return CoderResult.unmappableForLength(4);
}
if (jchar <= 0xffff) {
cArr[outIndex++] = (char) jchar;
outRemaining--;
} else {
if (outRemaining < 2) {
// Encoded with 4 bytes. inIndex currently points
// to the final byte. Move it back to first byte.
inIndex -= 3;
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.OVERFLOW;
}
cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
outRemaining -= 2;
}
}
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return (outRemaining == 0 && inIndex < inIndexLimit) ?
CoderResult.OVERFLOW :
CoderResult.UNDERFLOW;
}
}

View File

@@ -0,0 +1,235 @@
/*
* 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.tomcat.util.buf;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
/**
* Encodes characters as bytes using UTF-8. Extracted from Apache Harmony with
* some minor bug fixes applied.
*/
public class Utf8Encoder extends CharsetEncoder {
public Utf8Encoder() {
super(StandardCharsets.UTF_8, 1.1f, 4.0f);
}
@Override
protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
if (in.hasArray() && out.hasArray()) {
return encodeHasArray(in, out);
}
return encodeNotHasArray(in, out);
}
private CoderResult encodeHasArray(CharBuffer in, ByteBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
byte[] bArr;
char[] cArr;
int x = pos;
bArr = out.array();
cArr = in.array();
int outPos = out.position();
int rem = in.remaining();
for (x = pos; x < pos + rem; x++) {
int jchar = (cArr[x] & 0xFFFF);
if (jchar <= 0x7F) {
if (outRemaining < 1) {
in.position(x);
out.position(outPos);
return CoderResult.OVERFLOW;
}
bArr[outPos++] = (byte) (jchar & 0xFF);
outRemaining--;
} else if (jchar <= 0x7FF) {
if (outRemaining < 2) {
in.position(x);
out.position(outPos);
return CoderResult.OVERFLOW;
}
bArr[outPos++] = (byte) (0xC0 + ((jchar >> 6) & 0x1F));
bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
outRemaining -= 2;
} else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
// in has to have one byte more.
if (limit <= x + 1) {
in.position(x);
out.position(outPos);
return CoderResult.UNDERFLOW;
}
if (outRemaining < 4) {
in.position(x);
out.position(outPos);
return CoderResult.OVERFLOW;
}
// The surrogate pair starts with a low-surrogate.
if (jchar >= 0xDC00) {
in.position(x);
out.position(outPos);
return CoderResult.malformedForLength(1);
}
int jchar2 = cArr[x + 1] & 0xFFFF;
// The surrogate pair ends with a high-surrogate.
if (jchar2 < 0xDC00) {
in.position(x);
out.position(outPos);
return CoderResult.malformedForLength(1);
}
// Note, the Unicode scalar value n is defined
// as follows:
// n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
// Where jchar is a high-surrogate,
// jchar2 is a low-surrogate.
int n = (jchar << 10) + jchar2 + 0xFCA02400;
bArr[outPos++] = (byte) (0xF0 + ((n >> 18) & 0x07));
bArr[outPos++] = (byte) (0x80 + ((n >> 12) & 0x3F));
bArr[outPos++] = (byte) (0x80 + ((n >> 6) & 0x3F));
bArr[outPos++] = (byte) (0x80 + (n & 0x3F));
outRemaining -= 4;
x++;
} else {
if (outRemaining < 3) {
in.position(x);
out.position(outPos);
return CoderResult.OVERFLOW;
}
bArr[outPos++] = (byte) (0xE0 + ((jchar >> 12) & 0x0F));
bArr[outPos++] = (byte) (0x80 + ((jchar >> 6) & 0x3F));
bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
outRemaining -= 3;
}
if (outRemaining == 0) {
in.position(x + 1);
out.position(outPos);
// If both input and output are exhausted, return UNDERFLOW
if (x + 1 == limit) {
return CoderResult.UNDERFLOW;
} else {
return CoderResult.OVERFLOW;
}
}
}
if (rem != 0) {
in.position(x);
out.position(outPos);
}
return CoderResult.UNDERFLOW;
}
private CoderResult encodeNotHasArray(CharBuffer in, ByteBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
try {
while (pos < limit) {
if (outRemaining == 0) {
return CoderResult.OVERFLOW;
}
int jchar = (in.get() & 0xFFFF);
if (jchar <= 0x7F) {
if (outRemaining < 1) {
return CoderResult.OVERFLOW;
}
out.put((byte) jchar);
outRemaining--;
} else if (jchar <= 0x7FF) {
if (outRemaining < 2) {
return CoderResult.OVERFLOW;
}
out.put((byte) (0xC0 + ((jchar >> 6) & 0x1F)));
out.put((byte) (0x80 + (jchar & 0x3F)));
outRemaining -= 2;
} else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
// in has to have one byte more.
if (limit <= pos + 1) {
return CoderResult.UNDERFLOW;
}
if (outRemaining < 4) {
return CoderResult.OVERFLOW;
}
// The surrogate pair starts with a low-surrogate.
if (jchar >= 0xDC00) {
return CoderResult.malformedForLength(1);
}
int jchar2 = (in.get() & 0xFFFF);
// The surrogate pair ends with a high-surrogate.
if (jchar2 < 0xDC00) {
return CoderResult.malformedForLength(1);
}
// Note, the Unicode scalar value n is defined
// as follows:
// n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
// Where jchar is a high-surrogate,
// jchar2 is a low-surrogate.
int n = (jchar << 10) + jchar2 + 0xFCA02400;
out.put((byte) (0xF0 + ((n >> 18) & 0x07)));
out.put((byte) (0x80 + ((n >> 12) & 0x3F)));
out.put((byte) (0x80 + ((n >> 6) & 0x3F)));
out.put((byte) (0x80 + (n & 0x3F)));
outRemaining -= 4;
pos++;
} else {
if (outRemaining < 3) {
return CoderResult.OVERFLOW;
}
out.put((byte) (0xE0 + ((jchar >> 12) & 0x0F)));
out.put((byte) (0x80 + ((jchar >> 6) & 0x3F)));
out.put((byte) (0x80 + (jchar & 0x3F)));
outRemaining -= 3;
}
pos++;
}
} finally {
in.position(pos);
}
return CoderResult.UNDERFLOW;
}
}

View File

@@ -0,0 +1,37 @@
<!--
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.
-->
<html><body>
<H2>Buffers and Encodings</h2>
This package contains buffers and utils to perform encoding/decoding of buffers. That includes byte to char
conversions, URL encodings, etc.
<p>
Encoding is a critical operation for performance. There are few tricks in this package - the C2B and
B2C converters are caching an ISReader/OSWriter and keep everything allocated to do the conversions
in any VM without any garbage.
<p>
This package must accommodate future extensions and additional converters ( most important: the nio.charset,
which should be detected and used if available ). Also, we do have one hand-written UTF8Decoder, and
other tuned encoders could be added.
<p>
My benchmarks ( I'm costin :-) show only small differences between C2B, B2C and hand-written codders/decoders,
so UTF8Decoder may be disabled.
</body></html>

View File

@@ -0,0 +1,38 @@
/*
* 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.tomcat.util.codec;
/**
* Defines common decoding methods for byte array decoders.
*
* @deprecated This interface is unused and will be removed in Tomcat 9
*/
@Deprecated
public interface BinaryDecoder extends Decoder {
/**
* Decodes a byte array and returns the results as a byte array.
*
* @param source
* A byte array which has been encoded with the appropriate encoder
* @return a byte array that contains decoded content
* @throws DecoderException
* A decoder exception is thrown if a Decoder encounters a failure condition during the decode process.
*/
byte[] decode(byte[] source) throws DecoderException;
}

View File

@@ -0,0 +1,38 @@
/*
* 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.tomcat.util.codec;
/**
* Defines common encoding methods for byte array encoders.
*
* @deprecated This interface is unused and will be removed in Tomcat 9
*/
@Deprecated
public interface BinaryEncoder extends Encoder {
/**
* Encodes a byte array and return the encoded data as a byte array.
*
* @param source
* Data to be encoded
* @return A byte array containing the encoded data
* @throws EncoderException
* thrown if the Encoder encounters a failure condition during the encoding process.
*/
byte[] encode(byte[] source) throws EncoderException;
}

View File

@@ -0,0 +1,47 @@
/*
* 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.tomcat.util.codec;
/**
* Provides the highest level of abstraction for Decoders.
* <p>
* This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface.
* Allows a user to pass a generic Object to any Decoder implementation in the codec package.
* <p>
* One of the two interfaces at the center of the codec package.
*
* @deprecated This interface is unused and will be removed in Tomcat 9
*/
@Deprecated
public interface Decoder {
/**
* Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will
* try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a
* {@link ClassCastException} occurs this decode method will throw a DecoderException.
*
* @param source
* the object to decode
* @return a 'decoded" object
* @throws DecoderException
* a decoder exception can be thrown for any number of reasons. Some good candidates are that the
* parameter passed to this method is null, a param cannot be cast to the appropriate type for a
* specific encoder.
*/
Object decode(Object source) throws DecoderException;
}

View File

@@ -0,0 +1,86 @@
/*
* 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.tomcat.util.codec;
/**
* Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder}
* encounters a decoding specific exception such as invalid data, or characters outside of the expected range.
*
* @deprecated This exception is unused and will be removed in Tomcat 9
*/
@Deprecated
public class DecoderException extends Exception {
/**
* Declares the Serial Version Uid.
*
* @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a>
*/
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with <code>null</code> as its detail message. The cause is not initialized, and may
* subsequently be initialized by a call to {@link #initCause}.
*
* @since 1.4
*/
public DecoderException() {
super();
}
/**
* Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
* be initialized by a call to {@link #initCause}.
*
* @param message
* The detail message which is saved for later retrieval by the {@link #getMessage()} method.
*/
public DecoderException(final String message) {
super(message);
}
/**
* Constructs a new exception with the specified detail message and cause.
* <p>
* Note that the detail message associated with <code>cause</code> is not automatically incorporated into this
* exception's detail message.
*
* @param message
* The detail message which is saved for later retrieval by the {@link #getMessage()} method.
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A <code>null</code>
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public DecoderException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a new exception with the specified cause and a detail message of <code>(cause==null ?
* null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>).
* This constructor is useful for exceptions that are little more than wrappers for other throwables.
*
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A <code>null</code>
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public DecoderException(final Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.tomcat.util.codec;
/**
* Provides the highest level of abstraction for Encoders.
* <p>
* This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this
* common generic interface which allows a user to pass a generic Object to any Encoder implementation
* in the codec package.
*
* @deprecated This interface is unused and will be removed in Tomcat 9
*/
@Deprecated
public interface Encoder {
/**
* Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be
* <code>byte[]</code> or <code>String</code>s depending on the implementation used.
*
* @param source
* An object to encode
* @return An "encoded" Object
* @throws EncoderException
* An encoder exception is thrown if the encoder experiences a failure condition during the encoding
* process.
*/
Object encode(Object source) throws EncoderException;
}

View File

@@ -0,0 +1,89 @@
/*
* 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.tomcat.util.codec;
/**
* Thrown when there is a failure condition during the encoding process. This exception is thrown when an
* {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum,
* characters outside of the expected range.
*
* @deprecated This exception is unused and will be removed in Tomcat 9
*/
@Deprecated
public class EncoderException extends Exception {
/**
* Declares the Serial Version Uid.
*
* @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a>
*/
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with <code>null</code> as its detail message. The cause is not initialized, and may
* subsequently be initialized by a call to {@link #initCause}.
*
* @since 1.4
*/
public EncoderException() {
super();
}
/**
* Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
* be initialized by a call to {@link #initCause}.
*
* @param message
* a useful message relating to the encoder specific error.
*/
public EncoderException(final String message) {
super(message);
}
/**
* Constructs a new exception with the specified detail message and cause.
*
* <p>
* Note that the detail message associated with <code>cause</code> is not automatically incorporated into this
* exception's detail message.
* </p>
*
* @param message
* The detail message which is saved for later retrieval by the {@link #getMessage()} method.
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A <code>null</code>
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public EncoderException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a new exception with the specified cause and a detail message of <code>(cause==null ?
* null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>).
* This constructor is useful for exceptions that are little more than wrappers for other throwables.
*
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A <code>null</code>
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public EncoderException(final Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,817 @@
/*
* 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.tomcat.util.codec.binary;
import java.math.BigInteger;
/**
* Provides Base64 encoding and decoding as defined by <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
*
* <p>
* This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
* Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
* </p>
* <p>
* The class can be parameterized in the following manner with various constructors:
* </p>
* <ul>
* <li>URL-safe mode: Default off.</li>
* <li>Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of
* 4 in the encoded data.
* <li>Line separator: Default is CRLF ("\r\n")</li>
* </ul>
* <p>
* The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes.
* </p>
* <p>
* Since this class operates directly on byte streams, and not character streams, it is hard-coded to only
* encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252,
* UTF-8, etc).
* </p>
* <p>
* This class is thread-safe.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
* @since 1.0
*/
public class Base64 extends BaseNCodec {
/**
* BASE32 characters are 6 bits in length.
* They are formed by taking a block of 3 octets to form a 24-bit string,
* which is converted into 4 BASE64 characters.
*/
private static final int BITS_PER_ENCODED_BYTE = 6;
private static final int BYTES_PER_UNENCODED_BLOCK = 3;
private static final int BYTES_PER_ENCODED_BLOCK = 4;
/**
* Chunk separator per RFC 2045 section 2.1.
*
* <p>
* N.B. The next major release may break compatibility and make this field private.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
*/
static final byte[] CHUNK_SEPARATOR = {'\r', '\n'};
/**
* This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet"
* equivalents as specified in Table 1 of RFC 2045.
*
* Thanks to "commons" project in ws.apache.org for this code.
* https://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
private static final byte[] STANDARD_ENCODE_TABLE = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
/**
* This is a copy of the STANDARD_ENCODE_TABLE above, but with + and /
* changed to - and _ to make the encoded Base64 results more URL-SAFE.
* This table is only used when the Base64's mode is set to URL-SAFE.
*/
private static final byte[] URL_SAFE_ENCODE_TABLE = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};
/**
* This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified
* in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64
* alphabet but fall within the bounds of the array are translated to -1.
*
* Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both
* URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit).
*
* Thanks to "commons" project in ws.apache.org for this code.
* https://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
private static final byte[] STANDARD_DECODE_TABLE = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 20-2f + /
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 50-5f P-Z
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z
};
private static final byte[] URL_SAFE_DECODE_TABLE = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, // 20-2f -
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z
};
/**
* Base64 uses 6-bit fields.
*/
/** Mask used to extract 6 bits, used when encoding */
private static final int MASK_6BITS = 0x3f;
/** Mask used to extract 4 bits, used when decoding final trailing character. */
private static final int MASK_4BITS = 0xf;
/** Mask used to extract 2 bits, used when decoding final trailing character. */
private static final int MASK_2BITS = 0x3;
// The static final fields above are used for the original static byte[] methods on Base64.
// The private member fields below are used with the new streaming approach, which requires
// some state be preserved between calls of encode() and decode().
/**
* Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able
* to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch
* between the two modes.
*/
private final byte[] encodeTable;
// Only one decode table currently; keep for consistency with Base32 code
private final byte[] decodeTable;
/**
* Line separator for encoding. Not used when decoding. Only used if lineLength &gt; 0.
*/
private final byte[] lineSeparator;
/**
* Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
* <code>decodeSize = 3 + lineSeparator.length;</code>
*/
private final int decodeSize;
/**
* Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
* <code>encodeSize = 4 + lineSeparator.length;</code>
*/
private final int encodeSize;
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
* <p>
* When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE.
* </p>
*
* <p>
* When decoding all variants are supported.
* </p>
*/
public Base64() {
this(0);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode.
* <p>
* When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE.
* </p>
*
* <p>
* When decoding all variants are supported.
* </p>
*
* @param urlSafe
* if <code>true</code>, URL-safe encoding is used. In most cases this should be set to
* <code>false</code>.
* @since 1.4
*/
public Base64(final boolean urlSafe) {
this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
* <p>
* When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is
* STANDARD_ENCODE_TABLE.
* </p>
* <p>
* Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
* </p>
* <p>
* When decoding all variants are supported.
* </p>
*
* @param lineLength
* Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
* 4). If lineLength &lt;= 0, then the output will not be divided into lines (chunks). Ignored when
* decoding.
* @since 1.4
*/
public Base64(final int lineLength) {
this(lineLength, CHUNK_SEPARATOR);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
* <p>
* When encoding the line length and line separator are given in the constructor, and the encoding table is
* STANDARD_ENCODE_TABLE.
* </p>
* <p>
* Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
* </p>
* <p>
* When decoding all variants are supported.
* </p>
*
* @param lineLength
* Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
* 4). If lineLength &lt;= 0, then the output will not be divided into lines (chunks). Ignored when
* decoding.
* @param lineSeparator
* Each line of encoded data will end with this sequence of bytes.
* @throws IllegalArgumentException
* Thrown when the provided lineSeparator included some base64 characters.
* @since 1.4
*/
public Base64(final int lineLength, final byte[] lineSeparator) {
this(lineLength, lineSeparator, false);
}
/**
* Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
* <p>
* When encoding the line length and line separator are given in the constructor, and the encoding table is
* STANDARD_ENCODE_TABLE.
* </p>
* <p>
* Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
* </p>
* <p>
* When decoding all variants are supported.
* </p>
*
* @param lineLength
* Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
* 4). If lineLength &lt;= 0, then the output will not be divided into lines (chunks). Ignored when
* decoding.
* @param lineSeparator
* Each line of encoded data will end with this sequence of bytes.
* @param urlSafe
* Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode
* operations. Decoding seamlessly handles both modes.
* <b>Note: no padding is added when using the URL-safe alphabet.</b>
* @throws IllegalArgumentException
* The provided lineSeparator included some base64 characters. That's not going to work!
* @since 1.4
*/
public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) {
super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK,
lineLength,
lineSeparator == null ? 0 : lineSeparator.length);
// Needs to be set early to avoid NPE during call to containsAlphabetOrPad() below
this.decodeTable = urlSafe ? URL_SAFE_DECODE_TABLE : STANDARD_DECODE_TABLE;
// TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
// @see test case Base64Test.testConstructors()
if (lineSeparator != null) {
if (containsAlphabetOrPad(lineSeparator)) {
final String sep = StringUtils.newStringUtf8(lineSeparator);
throw new IllegalArgumentException(sm.getString("base64.lineSeparator", sep));
}
if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE
this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length;
this.lineSeparator = new byte[lineSeparator.length];
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
} else {
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
this.lineSeparator = null;
}
} else {
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
this.lineSeparator = null;
}
this.decodeSize = this.encodeSize - 1;
this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
}
/**
* Returns our current encode mode. True if we're URL-SAFE, false otherwise.
*
* @return true if we're in URL-SAFE mode, false otherwise.
* @since 1.4
*/
public boolean isUrlSafe() {
return this.encodeTable == URL_SAFE_ENCODE_TABLE;
}
/**
* <p>
* Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
* the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last
* remaining bytes (if not multiple of 3).
* </p>
* <p><b>Note: no padding is added when encoding using the URL-safe alphabet.</b></p>
* <p>
* Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
* https://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
* </p>
*
* @param in
* byte[] array of binary data to base64 encode.
* @param inPos
* Position to start reading data from.
* @param inAvail
* Amount of bytes available from input for encoding.
* @param context
* the context to be used
*/
@Override
void encode(final byte[] in, int inPos, final int inAvail, final Context context) {
if (context.eof) {
return;
}
// inAvail < 0 is how we're informed of EOF in the underlying data we're
// encoding.
if (inAvail < 0) {
context.eof = true;
if (0 == context.modulus && lineLength == 0) {
return; // no leftovers to process and not using chunking
}
final byte[] buffer = ensureBufferSize(encodeSize, context);
final int savedPos = context.pos;
switch (context.modulus) { // 0-2
case 0 : // nothing to do here
break;
case 1 : // 8 bits = 6 + 2
// top 6 bits:
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS];
// remaining 2:
buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS];
// URL-SAFE skips the padding to further reduce size.
if (encodeTable == STANDARD_ENCODE_TABLE) {
buffer[context.pos++] = pad;
buffer[context.pos++] = pad;
}
break;
case 2 : // 16 bits = 6 + 6 + 4
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS];
// URL-SAFE skips the padding to further reduce size.
if (encodeTable == STANDARD_ENCODE_TABLE) {
buffer[context.pos++] = pad;
}
break;
default:
throw new IllegalStateException(sm.getString(
"base64.impossibleModulus", Integer.valueOf(context.modulus)));
}
context.currentLinePos += context.pos - savedPos; // keep track of current line position
// if currentPos == 0 we are at the start of a line, so don't add CRLF
if (lineLength > 0 && context.currentLinePos > 0) {
System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
context.pos += lineSeparator.length;
}
} else {
for (int i = 0; i < inAvail; i++) {
final byte[] buffer = ensureBufferSize(encodeSize, context);
context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK;
int b = in[inPos++];
if (b < 0) {
b += 256;
}
context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE
if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS];
buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS];
buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS];
context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
if (lineLength > 0 && lineLength <= context.currentLinePos) {
System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
context.pos += lineSeparator.length;
context.currentLinePos = 0;
}
}
}
}
}
/**
* <p>
* Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
* with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
* call is not necessary when decoding, but it doesn't hurt, either.
* </p>
* <p>
* Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
* silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
* garbage-out philosophy: it will not check the provided data for validity.
* </p>
* <p>
* Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
* https://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
* </p>
*
* @param in
* byte[] array of ascii data to base64 decode.
* @param inPos
* Position to start reading data from.
* @param inAvail
* Amount of bytes available from input for decoding.
* @param context
* the context to be used
*/
@Override
void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
if (context.eof) {
return;
}
if (inAvail < 0) {
context.eof = true;
}
for (int i = 0; i < inAvail; i++) {
final byte[] buffer = ensureBufferSize(decodeSize, context);
final byte b = in[inPos++];
if (b == pad) {
// We're done.
context.eof = true;
break;
}
if (b >= 0 && b < decodeTable.length) {
final int result = decodeTable[b];
if (result >= 0) {
context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
if (context.modulus == 0) {
buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
}
}
}
}
// Two forms of EOF as far as base64 decoder is concerned: actual
// EOF (-1) and first time '=' character is encountered in stream.
// This approach makes the '=' padding characters completely optional.
if (context.eof && context.modulus != 0) {
final byte[] buffer = ensureBufferSize(decodeSize, context);
// We have some spare bits remaining
// Output all whole multiples of 8 bits and ignore the rest
switch (context.modulus) {
// case 0 : // impossible, as excluded above
case 1 : // 6 bits - ignore entirely
// TODO not currently tested; perhaps it is impossible?
break;
case 2 : // 12 bits = 8 + 4
validateCharacter(MASK_4BITS, context);
context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
break;
case 3 : // 18 bits = 8 + 8 + 2
validateCharacter(MASK_2BITS, context);
context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
break;
default:
throw new IllegalStateException(sm.getString(
"base64.impossibleModulus", Integer.valueOf(context.modulus)));
}
}
}
/**
* Returns whether or not the <code>octet</code> is in the base 64 alphabet.
*
* @param octet
* The value to test
* @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
* @since 1.4
*/
public static boolean isBase64(final byte octet) {
return octet == PAD_DEFAULT || (octet >= 0 && octet < STANDARD_DECODE_TABLE.length && STANDARD_DECODE_TABLE[octet] != -1);
}
/**
* Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the
* method treats whitespace as valid.
*
* @param base64
* String to test
* @return <code>true</code> if all characters in the String are valid characters in the Base64 alphabet or if
* the String is empty; <code>false</code>, otherwise
* @since 1.5
*/
public static boolean isBase64(final String base64) {
return isBase64(StringUtils.getBytesUtf8(base64));
}
/**
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
* method treats whitespace as valid.
*
* @param arrayOctet
* byte array to test
* @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
* <code>false</code>, otherwise
* @since 1.5
*/
public static boolean isBase64(final byte[] arrayOctet) {
for (int i = 0; i < arrayOctet.length; i++) {
if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
return false;
}
}
return true;
}
/**
* Encodes binary data using the base64 algorithm but does not chunk the output.
*
* @param binaryData
* binary data to encode
* @return byte[] containing Base64 characters in their UTF-8 representation.
*/
public static byte[] encodeBase64(final byte[] binaryData) {
return encodeBase64(binaryData, false);
}
/**
* Encodes binary data using the base64 algorithm but does not chunk the output.
*
* NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to
* single-line non-chunking (commons-codec-1.5).
*
* @param binaryData
* binary data to encode
* @return String containing Base64 characters.
* @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not).
*/
public static String encodeBase64String(final byte[] binaryData) {
return StringUtils.newStringUsAscii(encodeBase64(binaryData, false));
}
/**
* Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
* url-safe variation emits - and _ instead of + and / characters.
* <b>Note: no padding is added.</b>
* @param binaryData
* binary data to encode
* @return byte[] containing Base64 characters in their UTF-8 representation.
* @since 1.4
*/
public static byte[] encodeBase64URLSafe(final byte[] binaryData) {
return encodeBase64(binaryData, false, true);
}
/**
* Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
* url-safe variation emits - and _ instead of + and / characters.
* <b>Note: no padding is added.</b>
* @param binaryData
* binary data to encode
* @return String containing Base64 characters
* @since 1.4
*/
public static String encodeBase64URLSafeString(final byte[] binaryData) {
return StringUtils.newStringUsAscii(encodeBase64(binaryData, false, true));
}
/**
* Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
*
* @param binaryData
* binary data to encode
* @return Base64 characters chunked in 76 character blocks
*/
public static byte[] encodeBase64Chunked(final byte[] binaryData) {
return encodeBase64(binaryData, true);
}
/**
* Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
*
* @param binaryData
* Array containing binary data to encode.
* @param isChunked
* if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
* @return Base64-encoded data.
* @throws IllegalArgumentException
* Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
*/
public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) {
return encodeBase64(binaryData, isChunked, false);
}
/**
* Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
*
* @param binaryData
* Array containing binary data to encode.
* @param isChunked
* if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
* @param urlSafe
* if <code>true</code> this encoder will emit - and _ instead of the usual + and / characters.
* <b>Note: no padding is added when encoding using the URL-safe alphabet.</b>
* @return Base64-encoded data.
* @throws IllegalArgumentException
* Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
* @since 1.4
*/
public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) {
return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE);
}
/**
* Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
*
* @param binaryData
* Array containing binary data to encode.
* @param isChunked
* if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
* @param urlSafe
* if <code>true</code> this encoder will emit - and _ instead of the usual + and / characters.
* <b>Note: no padding is added when encoding using the URL-safe alphabet.</b>
* @param maxResultSize
* The maximum result size to accept.
* @return Base64-encoded data.
* @throws IllegalArgumentException
* Thrown when the input array needs an output array bigger than maxResultSize
* @since 1.4
*/
public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked,
final boolean urlSafe, final int maxResultSize) {
if (binaryData == null || binaryData.length == 0) {
return binaryData;
}
// Create this so can use the super-class method
// Also ensures that the same roundings are performed by the ctor and the code
final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe);
final long len = b64.getEncodedLength(binaryData);
if (len > maxResultSize) {
throw new IllegalArgumentException(sm.getString(
"base64.inputTooLarge", Long.valueOf(len), Integer.valueOf(maxResultSize)));
}
return b64.encode(binaryData);
}
/**
* Decodes a Base64 String into octets.
* <p>
* <b>Note:</b> this method seamlessly handles data encoded in URL-safe or normal mode.
* </p>
*
* @param base64String
* String containing Base64 data
* @return Array containing decoded data.
* @since 1.4
*/
public static byte[] decodeBase64(final String base64String) {
return new Base64().decode(base64String);
}
public static byte[] decodeBase64URLSafe(final String base64String) {
return new Base64(true).decode(base64String);
}
/**
* Decodes Base64 data into octets.
* <p>
* <b>Note:</b> this method seamlessly handles data encoded in URL-safe or normal mode.
* </p>
*
* @param base64Data
* Byte array containing Base64 data
* @return Array containing decoded data.
*/
public static byte[] decodeBase64(final byte[] base64Data) {
return decodeBase64(base64Data, 0, base64Data.length);
}
public static byte[] decodeBase64(
final byte[] base64Data, final int off, final int len) {
return new Base64().decode(base64Data, off, len);
}
// Implementation of the Encoder Interface
// Implementation of integer encoding used for crypto
/**
* Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature.
*
* @param pArray
* a byte array containing base64 character data
* @return A BigInteger
* @since 1.4
*/
public static BigInteger decodeInteger(final byte[] pArray) {
return new BigInteger(1, decodeBase64(pArray));
}
/**
* Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature.
*
* @param bigInt
* a BigInteger
* @return A byte array containing base64 character data
* @throws NullPointerException
* if null is passed in
* @since 1.4
*/
public static byte[] encodeInteger(final BigInteger bigInt) {
if (bigInt == null) {
throw new NullPointerException(sm.getString("base64.nullEncodeParameter"));
}
return encodeBase64(toIntegerBytes(bigInt), false);
}
/**
* Returns a byte-array representation of a <code>BigInteger</code> without sign bit.
*
* @param bigInt
* <code>BigInteger</code> to be converted
* @return a byte array representation of the BigInteger parameter
*/
static byte[] toIntegerBytes(final BigInteger bigInt) {
int bitlen = bigInt.bitLength();
// round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
final byte[] bigBytes = bigInt.toByteArray();
if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
}
// set up params for copying everything but sign bit
int startSrc = 0;
int len = bigBytes.length;
// if bigInt is exactly byte-aligned, just skip signbit in copy
if ((bigInt.bitLength() % 8) == 0) {
startSrc = 1;
len--;
}
final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
final byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
return resizedBytes;
}
/**
* Returns whether or not the <code>octet</code> is in the Base64 alphabet.
*
* @param octet
* The value to test
* @return <code>true</code> if the value is defined in the the Base64 alphabet <code>false</code> otherwise.
*/
@Override
protected boolean isInAlphabet(final byte octet) {
return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1;
}
/**
* Validates whether decoding the final trailing character is possible in the context
* of the set of possible base 64 values.
*
* <p>The character is valid if the lower bits within the provided mask are zero. This
* is used to test the final trailing base-64 digit is zero in the bits that will be discarded.
*
* @param emptyBitsMask The mask of the lower bits that should be empty
* @param context the context to be used
*
* @throws IllegalArgumentException if the bits being checked contain any non-zero value
*/
private static void validateCharacter(final int emptyBitsMask, final Context context) {
if ((context.ibitWorkArea & emptyBitsMask) != 0) {
throw new IllegalArgumentException(
"Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value. " +
"Expected the discarded bits to be zero.");
}
}
}

View File

@@ -0,0 +1,615 @@
/*
* 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.tomcat.util.codec.binary;
import org.apache.tomcat.util.buf.HexUtils;
import org.apache.tomcat.util.codec.BinaryDecoder;
import org.apache.tomcat.util.codec.BinaryEncoder;
import org.apache.tomcat.util.codec.DecoderException;
import org.apache.tomcat.util.codec.EncoderException;
import org.apache.tomcat.util.res.StringManager;
/**
* Abstract superclass for Base-N encoders and decoders.
*
* <p>
* This class is thread-safe.
* </p>
*/
@SuppressWarnings("deprecation")
public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder {
protected static final StringManager sm = StringManager.getManager(BaseNCodec.class);
/**
* Holds thread context so classes can be thread-safe.
*
* This class is not itself thread-safe; each thread must allocate its own copy.
*
* @since 1.7
*/
static class Context {
/**
* Place holder for the bytes we're dealing with for our based logic.
* Bitwise operations store and extract the encoding or decoding from this variable.
*/
int ibitWorkArea;
/**
* Buffer for streaming.
*/
byte[] buffer;
/**
* Position where next character should be written in the buffer.
*/
int pos;
/**
* Position where next character should be read from the buffer.
*/
int readPos;
/**
* Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
* and must be thrown away.
*/
boolean eof;
/**
* Variable tracks how many characters have been written to the current line. Only used when encoding. We use
* it to make sure each encoded line never goes beyond lineLength (if lineLength &gt; 0).
*/
int currentLinePos;
/**
* Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This
* variable helps track that.
*/
int modulus;
Context() {
}
/**
* Returns a String useful for debugging (especially within a debugger.)
*
* @return a String useful for debugging.
*/
@SuppressWarnings("boxing") // OK to ignore boxing here
@Override
public String toString() {
return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, " +
"ibitWorkArea=%s, modulus=%s, pos=%s, " +
"readPos=%s]", this.getClass().getSimpleName(),
HexUtils.toHexString(buffer), currentLinePos, eof,
ibitWorkArea, modulus, pos, readPos);
}
}
/**
* EOF
*
* @since 1.7
*/
static final int EOF = -1;
/**
* MIME chunk size per RFC 2045 section 6.8.
*
* <p>
* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
* equal signs.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
*/
public static final int MIME_CHUNK_SIZE = 76;
/**
* PEM chunk size per RFC 1421 section 4.3.2.4.
*
* <p>
* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
* equal signs.
* </p>
*
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421 section 4.3.2.4</a>
*/
public static final int PEM_CHUNK_SIZE = 64;
private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
/**
* Defines the default buffer size - currently {@value}
* - must be large enough for at least one encoded block+separator
*/
private static final int DEFAULT_BUFFER_SIZE = 128;
/**
* The maximum size buffer to allocate.
*
* <p>This is set to the same size used in the JDK {@code java.util.ArrayList}:</p>
* <blockquote>
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit.
* </blockquote>
*/
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
/** Mask used to extract 8 bits, used in decoding bytes */
protected static final int MASK_8BITS = 0xff;
/**
* Byte used to pad output.
*/
protected static final byte PAD_DEFAULT = '='; // Allow static access to default
protected final byte pad; // instance variable just in case it needs to vary later
/** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */
private final int unencodedBlockSize;
/** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */
private final int encodedBlockSize;
/**
* Chunksize for encoding. Not used when decoding.
* A value of zero or less implies no chunking of the encoded data.
* Rounded down to nearest multiple of encodedBlockSize.
*/
protected final int lineLength;
/**
* Size of chunk separator. Not used unless {@link #lineLength} &gt; 0.
*/
private final int chunkSeparatorLength;
/**
* Note <code>lineLength</code> is rounded down to the nearest multiple of the encoded block size.
* If <code>chunkSeparatorLength</code> is zero, then chunking is disabled.
* @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
* @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
* @param lineLength if &gt; 0, use chunking with a length <code>lineLength</code>
* @param chunkSeparatorLength the chunk separator length, if relevant
*/
protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
final int lineLength, final int chunkSeparatorLength) {
this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT);
}
/**
* Note <code>lineLength</code> is rounded down to the nearest multiple of the encoded block size.
* If <code>chunkSeparatorLength</code> is zero, then chunking is disabled.
* @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
* @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
* @param lineLength if &gt; 0, use chunking with a length <code>lineLength</code>
* @param chunkSeparatorLength the chunk separator length, if relevant
* @param pad byte used as padding byte.
*/
protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
final int lineLength, final int chunkSeparatorLength, final byte pad) {
this.unencodedBlockSize = unencodedBlockSize;
this.encodedBlockSize = encodedBlockSize;
final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0;
this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0;
this.chunkSeparatorLength = chunkSeparatorLength;
this.pad = pad;
}
/**
* Returns true if this object has buffered data for reading.
*
* @param context the context to be used
* @return true if there is data still available for reading.
*/
boolean hasData(final Context context) { // package protected for access from I/O streams
return context.buffer != null;
}
/**
* Returns the amount of buffered data available for reading.
*
* @param context the context to be used
* @return The amount of buffered data available for reading.
*/
int available(final Context context) { // package protected for access from I/O streams
return context.buffer != null ? context.pos - context.readPos : 0;
}
/**
* Get the default buffer size. Can be overridden.
*
* @return the default buffer size.
*/
protected int getDefaultBufferSize() {
return DEFAULT_BUFFER_SIZE;
}
/**
* Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
* @param context the context to be used
* @param minCapacity the minimum required capacity
* @return the resized byte[] buffer
* @throws OutOfMemoryError if the {@code minCapacity} is negative
*/
private static byte[] resizeBuffer(final Context context, final int minCapacity) {
// Overflow-conscious code treats the min and new capacity as unsigned.
final int oldCapacity = context.buffer.length;
int newCapacity = oldCapacity * DEFAULT_BUFFER_RESIZE_FACTOR;
if (compareUnsigned(newCapacity, minCapacity) < 0) {
newCapacity = minCapacity;
}
if (compareUnsigned(newCapacity, MAX_BUFFER_SIZE) > 0) {
newCapacity = createPositiveCapacity(minCapacity);
}
final byte[] b = new byte[newCapacity];
System.arraycopy(context.buffer, 0, b, 0, context.buffer.length);
context.buffer = b;
return b;
}
/**
* Compares two {@code int} values numerically treating the values
* as unsigned. Taken from JDK 1.8.
*
* <p>TODO: Replace with JDK 1.8 Integer::compareUnsigned(int, int).</p>
*
* @param x the first {@code int} to compare
* @param y the second {@code int} to compare
* @return the value {@code 0} if {@code x == y}; a value less
* than {@code 0} if {@code x < y} as unsigned values; and
* a value greater than {@code 0} if {@code x > y} as
* unsigned values
*/
private static int compareUnsigned(int x, int y) {
return Integer.compare(x + Integer.MIN_VALUE, y + Integer.MIN_VALUE);
}
/**
* Create a positive capacity at least as large the minimum required capacity.
* If the minimum capacity is negative then this throws an OutOfMemoryError as no array
* can be allocated.
*
* @param minCapacity the minimum capacity
* @return the capacity
* @throws OutOfMemoryError if the {@code minCapacity} is negative
*/
private static int createPositiveCapacity(int minCapacity) {
if (minCapacity < 0) {
// overflow
throw new OutOfMemoryError("Unable to allocate array size: " + (minCapacity & 0xffffffffL));
}
// This is called when we require buffer expansion to a very big array.
// Use the conservative maximum buffer size if possible, otherwise the biggest required.
//
// Note: In this situation JDK 1.8 java.util.ArrayList returns Integer.MAX_VALUE.
// This excludes some VMs that can exceed MAX_BUFFER_SIZE but not allocate a full
// Integer.MAX_VALUE length array.
// The result is that we may have to allocate an array of this size more than once if
// the capacity must be expanded again.
return (minCapacity > MAX_BUFFER_SIZE) ?
minCapacity :
MAX_BUFFER_SIZE;
}
/**
* Ensure that the buffer has room for <code>size</code> bytes
*
* @param size minimum spare space required
* @param context the context to be used
* @return the buffer
*/
protected byte[] ensureBufferSize(final int size, final Context context){
if (context.buffer == null) {
context.buffer = new byte[getDefaultBufferSize()];
context.pos = 0;
context.readPos = 0;
// Overflow-conscious:
// x + y > z == x + y - z > 0
} else if (context.pos + size - context.buffer.length > 0) {
return resizeBuffer(context, context.pos + size);
}
return context.buffer;
}
/**
* Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail
* bytes. Returns how many bytes were actually extracted.
* <p>
* Package protected for access from I/O streams.
*
* @param b
* byte[] array to extract the buffered data into.
* @param bPos
* position in byte[] array to start extraction at.
* @param bAvail
* amount of bytes we're allowed to extract. We may extract fewer (if fewer are available).
* @param context
* the context to be used
* @return The number of bytes successfully extracted into the provided byte[] array.
*/
int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) {
if (context.buffer != null) {
final int len = Math.min(available(context), bAvail);
System.arraycopy(context.buffer, context.readPos, b, bPos, len);
context.readPos += len;
if (context.readPos >= context.pos) {
context.buffer = null; // so hasData() will return false, and this method can return -1
}
return len;
}
return context.eof ? EOF : 0;
}
/**
* Checks if a byte value is whitespace or not.
* Whitespace is taken to mean: space, tab, CR, LF
* @param byteToCheck
* the byte to check
* @return true if byte is whitespace, false otherwise
*/
protected static boolean isWhiteSpace(final byte byteToCheck) {
switch (byteToCheck) {
case ' ' :
case '\n' :
case '\r' :
case '\t' :
return true;
default :
return false;
}
}
/**
* Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
* the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
*
* @param obj
* Object to encode
* @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied.
* @throws EncoderException
* if the parameter supplied is not of type byte[]
* @deprecated This unused method will be removed in Tomcat 9
*/
@Override
@Deprecated
public Object encode(final Object obj) throws EncoderException {
if (!(obj instanceof byte[])) {
throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]");
}
return encode((byte[]) obj);
}
/**
* Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
* Uses UTF8 encoding.
*
* @param pArray
* a byte array containing binary data
* @return A String containing only Base-N character data
*/
public String encodeToString(final byte[] pArray) {
return StringUtils.newStringUtf8(encode(pArray));
}
/**
* Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet.
* Uses UTF8 encoding.
*
* @param pArray a byte array containing binary data
* @return String containing only character data in the appropriate alphabet.
* @since 1.5
* This is a duplicate of {@link #encodeToString(byte[])}; it was merged during refactoring.
*/
public String encodeAsString(final byte[] pArray){
return StringUtils.newStringUtf8(encode(pArray));
}
/**
* Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
* the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String.
*
* @param obj
* Object to decode
* @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String
* supplied.
* @throws DecoderException
* if the parameter supplied is not of type byte[]
* @deprecated This unused method will be removed in Tomcat 9
*/
@Override
@Deprecated
public Object decode(final Object obj) throws DecoderException {
if (obj instanceof byte[]) {
return decode((byte[]) obj);
} else if (obj instanceof String) {
return decode((String) obj);
} else {
throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String");
}
}
/**
* Decodes a String containing characters in the Base-N alphabet.
*
* @param pArray
* A String containing Base-N character data
* @return a byte array containing binary data
*/
public byte[] decode(final String pArray) {
return decode(StringUtils.getBytesUtf8(pArray));
}
/**
* Decodes a byte[] containing characters in the Base-N alphabet.
*
* @param pArray
* A byte array containing Base-N character data
* @return a byte array containing binary data
*/
@Override
public byte[] decode(final byte[] pArray) {
return decode(pArray, 0, pArray.length);
}
public byte[] decode(final byte[] pArray, final int off, final int len) {
if (pArray == null || len == 0) {
return new byte[0];
}
final Context context = new Context();
decode(pArray, off, len, context);
decode(pArray, off, EOF, context); // Notify decoder of EOF.
final byte[] result = new byte[context.pos];
readResults(result, 0, result.length, context);
return result;
}
/**
* Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
*
* @param pArray
* a byte array containing binary data
* @return A byte array containing only the base N alphabetic character data
*/
@Override
public byte[] encode(final byte[] pArray) {
if (pArray == null || pArray.length == 0) {
return pArray;
}
return encode(pArray, 0, pArray.length);
}
/**
* Encodes a byte[] containing binary data, into a byte[] containing
* characters in the alphabet.
*
* @param pArray
* a byte array containing binary data
* @param offset
* initial offset of the subarray.
* @param length
* length of the subarray.
* @return A byte array containing only the base N alphabetic character data
* @since 1.11
*/
public byte[] encode(final byte[] pArray, final int offset, final int length) {
if (pArray == null || pArray.length == 0) {
return pArray;
}
final Context context = new Context();
encode(pArray, offset, length, context);
encode(pArray, offset, EOF, context); // Notify encoder of EOF.
final byte[] buf = new byte[context.pos - context.readPos];
readResults(buf, 0, buf.length, context);
return buf;
}
// package protected for access from I/O streams
abstract void encode(byte[] pArray, int i, int length, Context context);
// package protected for access from I/O streams
abstract void decode(byte[] pArray, int i, int length, Context context);
/**
* Returns whether or not the <code>octet</code> is in the current alphabet.
* Does not allow whitespace or pad.
*
* @param value The value to test
*
* @return <code>true</code> if the value is defined in the current alphabet, <code>false</code> otherwise.
*/
protected abstract boolean isInAlphabet(byte value);
/**
* Tests a given byte array to see if it contains only valid characters within the alphabet.
* The method optionally treats whitespace and pad as valid.
*
* @param arrayOctet byte array to test
* @param allowWSPad if <code>true</code>, then whitespace and PAD are also allowed
*
* @return <code>true</code> if all bytes are valid characters in the alphabet or if the byte array is empty;
* <code>false</code>, otherwise
*/
public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) {
for (final byte octet : arrayOctet) {
if (!isInAlphabet(octet) &&
(!allowWSPad || (octet != pad) && !isWhiteSpace(octet))) {
return false;
}
}
return true;
}
/**
* Tests a given String to see if it contains only valid characters within the alphabet.
* The method treats whitespace and PAD as valid.
*
* @param basen String to test
* @return <code>true</code> if all characters in the String are valid characters in the alphabet or if
* the String is empty; <code>false</code>, otherwise
* @see #isInAlphabet(byte[], boolean)
*/
public boolean isInAlphabet(final String basen) {
return isInAlphabet(StringUtils.getBytesUtf8(basen), true);
}
/**
* Tests a given byte array to see if it contains any characters within the alphabet or PAD.
*
* Intended for use in checking line-ending arrays
*
* @param arrayOctet
* byte array to test
* @return <code>true</code> if any byte is a valid character in the alphabet or PAD; <code>false</code> otherwise
*/
protected boolean containsAlphabetOrPad(final byte[] arrayOctet) {
if (arrayOctet == null) {
return false;
}
for (final byte element : arrayOctet) {
if (pad == element || isInAlphabet(element)) {
return true;
}
}
return false;
}
/**
* Calculates the amount of space needed to encode the supplied array.
*
* @param pArray byte[] array which will later be encoded
*
* @return amount of space needed to encoded the supplied array.
* Returns a long since a max-len array will require &gt; Integer.MAX_VALUE
*/
public long getEncodedLength(final byte[] pArray) {
// Calculate non-chunked size - rounded up to allow for padding
// cast to long is needed to avoid possibility of overflow
long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize;
if (lineLength > 0) { // We're using chunking
// Round up to nearest multiple
len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength;
}
return len;
}
}

View File

@@ -0,0 +1,19 @@
# 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.
base64.impossibleModulus=Impossible modulus [{0}]
base64.inputTooLarge=Input array too large, the output array would be bigger [{0}] than the specified maximum size of [{1}]
base64.lineSeparator=Line separator must not contain base64 characters [{0}]
base64.nullEncodeParameter=Cannot encode integer with null parameter

View File

@@ -0,0 +1,19 @@
# 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.
base64.impossibleModulus=Modulo [{0}] invalide
base64.inputTooLarge=Le tableau en entrée est trop grand, la taille du tableau de sortie [{0}] serait plus grande que la taille maximale autorisée [{1}]
base64.lineSeparator=Le séparateur de ligne ne doit pas contenir des caractères base64 [{0}]
base64.nullEncodeParameter=Impossible d'encoder unentier en utilisant un paramètre null

View File

@@ -0,0 +1,19 @@
# 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.
base64.impossibleModulus=計算できない剰余 [{0}] です。
base64.inputTooLarge=入力配列が大きすぎます。出力配列は[{1}]の指定された最大サイズよりも大きくなります[{0}]。
base64.lineSeparator=行区切り記号にはbase64文字を使用できません[{0}]
base64.nullEncodeParameter=null 値を整数に符号化できませんでした。

View File

@@ -0,0 +1,19 @@
# 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.
base64.impossibleModulus=불가능한 계수 [{0}]
base64.inputTooLarge=입력 배열이 너무 큽니다. 출력 배열의 크기 [{0}]이(가), 지정된 최대 크기 [{1}] 보다 큰 값입니다.
base64.lineSeparator=행 구분문자 [{0}]은(는) Base64 문자들을 포함해서는 안됩니다.
base64.nullEncodeParameter=정수를 위한 파라미터가 널이라서 인코딩할 수 없습니다.

View File

@@ -0,0 +1,100 @@
/*
* 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.tomcat.util.codec.binary;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Converts String to and from bytes using the encodings required by the Java specification. These encodings are
* specified in <a href="http://download.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">
* Standard charsets</a>.
*
* <p>This class is immutable and thread-safe.</p>
*
* @see <a href="http://download.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
* @since 1.4
*/
public class StringUtils {
/**
* Calls {@link String#getBytes(Charset)}
*
* @param string
* The string to encode (if null, return null).
* @param charset
* The {@link Charset} to encode the <code>String</code>
* @return the encoded bytes
*/
private static byte[] getBytes(final String string, final Charset charset) {
if (string == null) {
return null;
}
return string.getBytes(charset);
}
/**
* Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte
* array.
*
* @param string
* the String to encode, may be <code>null</code>
* @return encoded bytes, or <code>null</code> if the input string was <code>null</code>
* @see <a href="http://download.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
*/
public static byte[] getBytesUtf8(final String string) {
return getBytes(string, StandardCharsets.UTF_8);
}
/**
* Constructs a new <code>String</code> by decoding the specified array of bytes using the given charset.
*
* @param bytes
* The bytes to be decoded into characters
* @param charset
* The {@link Charset} to encode the <code>String</code>
* @return A new <code>String</code> decoded from the specified array of bytes using the given charset,
* or <code>null</code> if the input byte array was <code>null</code>.
*/
private static String newString(final byte[] bytes, final Charset charset) {
return bytes == null ? null : new String(bytes, charset);
}
/**
* Constructs a new <code>String</code> by decoding the specified array of bytes using the US-ASCII charset.
*
* @param bytes
* The bytes to be decoded into characters
* @return A new <code>String</code> decoded from the specified array of bytes using the US-ASCII charset,
* or <code>null</code> if the input byte array was <code>null</code>.
*/
public static String newStringUsAscii(final byte[] bytes) {
return newString(bytes, StandardCharsets.US_ASCII);
}
/**
* Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-8 charset.
*
* @param bytes
* The bytes to be decoded into characters
* @return A new <code>String</code> decoded from the specified array of bytes using the UTF-8 charset,
* or <code>null</code> if the input byte array was <code>null</code>.
*/
public static String newStringUtf8(final byte[] bytes) {
return newString(bytes, StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,21 @@
<!--
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.
-->
<html>
<body>
Base64, Base32, Binary, and Hexadecimal String encoding and decoding.
</body>
</html>

View File

@@ -0,0 +1,208 @@
/*
* 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.tomcat.util.collections;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.tomcat.util.res.StringManager;
/**
* A Map implementation that uses case-insensitive (using {@link
* Locale#ENGLISH}) strings as keys.
* <p>
* Keys must be instances of {@link String}. Note that this means that
* <code>null</code> keys are not permitted.
* <p>
* This implementation is not thread-safe.
*
* @param <V> Type of values placed in this Map.
*/
public class CaseInsensitiveKeyMap<V> extends AbstractMap<String,V> {
private static final StringManager sm =
StringManager.getManager(CaseInsensitiveKeyMap.class);
private final Map<Key,V> map = new HashMap<>();
@Override
public V get(Object key) {
return map.get(Key.getInstance(key));
}
@Override
public V put(String key, V value) {
Key caseInsensitiveKey = Key.getInstance(key);
if (caseInsensitiveKey == null) {
throw new NullPointerException(sm.getString("caseInsensitiveKeyMap.nullKey"));
}
return map.put(caseInsensitiveKey, value);
}
/**
* {@inheritDoc}
* <p>
* <b>Use this method with caution</b>. If the input Map contains duplicate
* keys when the keys are compared in a case insensitive manner then some
* values will be lost when inserting via this method.
*/
@Override
public void putAll(Map<? extends String, ? extends V> m) {
super.putAll(m);
}
@Override
public boolean containsKey(Object key) {
return map.containsKey(Key.getInstance(key));
}
@Override
public V remove(Object key) {
return map.remove(Key.getInstance(key));
}
@Override
public Set<Entry<String, V>> entrySet() {
return new EntrySet<>(map.entrySet());
}
private static class EntrySet<V> extends AbstractSet<Entry<String,V>> {
private final Set<Entry<Key,V>> entrySet;
public EntrySet(Set<Map.Entry<Key,V>> entrySet) {
this.entrySet = entrySet;
}
@Override
public Iterator<Entry<String,V>> iterator() {
return new EntryIterator<>(entrySet.iterator());
}
@Override
public int size() {
return entrySet.size();
}
}
private static class EntryIterator<V> implements Iterator<Entry<String,V>> {
private final Iterator<Entry<Key,V>> iterator;
public EntryIterator(Iterator<Entry<Key,V>> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Entry<String,V> next() {
Entry<Key,V> entry = iterator.next();
return new EntryImpl<>(entry.getKey().getKey(), entry.getValue());
}
@Override
public void remove() {
iterator.remove();
}
}
private static class EntryImpl<V> implements Entry<String,V> {
private final String key;
private final V value;
public EntryImpl(String key, V value) {
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw new UnsupportedOperationException();
}
}
private static class Key {
private final String key;
private final String lcKey;
private Key(String key) {
this.key = key;
this.lcKey = key.toLowerCase(Locale.ENGLISH);
}
public String getKey() {
return key;
}
@Override
public int hashCode() {
return lcKey.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Key other = (Key) obj;
return lcKey.equals(other.lcKey);
}
public static Key getInstance(Object o) {
if (o instanceof String) {
return new Key((String) o);
}
return null;
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.tomcat.util.collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
public final class ConcurrentCache<K,V> {
private final int size;
private final Map<K,V> eden;
private final Map<K,V> longterm;
public ConcurrentCache(int size) {
this.size = size;
this.eden = new ConcurrentHashMap<>(size);
this.longterm = new WeakHashMap<>(size);
}
public V get(K k) {
V v = this.eden.get(k);
if (v == null) {
synchronized (longterm) {
v = this.longterm.get(k);
}
if (v != null) {
this.eden.put(k, v);
}
}
return v;
}
public void put(K k, V v) {
if (this.eden.size() >= size) {
synchronized (longterm) {
this.longterm.putAll(this.eden);
}
this.eden.clear();
}
this.eden.put(k, v);
}
}

View File

@@ -0,0 +1,271 @@
/*
* 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.tomcat.util.collections;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Concurrent hash map that holds its keys via weak references. Unlike
* <code>WeakHashMap</code> this class does not handle dead keys during common
* access operations, but expects you to call its {@link #maintain()} method
* periodically. Both keys and values are expected to be not-<code>null</code>.
*
* @param <K> The type of keys used with the Map instance
* @param <V> The type of values used with the Map instance
*/
public class ManagedConcurrentWeakHashMap<K, V> extends AbstractMap<K, V> implements
ConcurrentMap<K, V> {
private final ConcurrentMap<Key, V> map = new ConcurrentHashMap<>();
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
/**
* Method, that has to be invoked periodically to clean dead keys from the
* map.
*/
public void maintain() {
Key key;
while ((key = (Key) queue.poll()) != null) {
if (key.isDead()) {
// No need to lookup if the key is not in the map
continue;
}
key.ackDeath();
map.remove(key);
}
}
private static class Key extends WeakReference<Object> {
private final int hash;
private boolean dead;
public Key(Object key, ReferenceQueue<Object> queue) {
super(key, queue);
hash = key.hashCode();
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (dead) {
// Post-mortem cleanup looks for this specific Reference
// instance
return false;
}
if (!(obj instanceof Reference<?>)) {
return false;
}
Object oA = get();
Object oB = ((Reference<?>) obj).get();
if (oA == oB) {
return true;
}
if (oA == null || oB == null) {
return false;
}
return oA.equals(oB);
}
public void ackDeath() {
this.dead = true;
}
public boolean isDead() {
return dead;
}
}
/**
* Creates Key instance to be used to store values in the map. It is
* registered with the ReferenceQueue.
*/
private Key createStoreKey(Object key) {
return new Key(key, queue);
}
/**
* Creates Key instance to be used only to lookup values in the map. It is
* not registered with the ReferenceQueue.
*/
private Key createLookupKey(Object key) {
return new Key(key, null);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsValue(Object value) {
if (value == null) {
return false;
}
return map.containsValue(value);
}
@Override
public boolean containsKey(Object key) {
if (key == null) {
return false;
}
return map.containsKey(createLookupKey(key));
}
@Override
public V get(Object key) {
if (key == null) {
return null;
}
return map.get(createLookupKey(key));
}
@Override
public V put(K key, V value) {
Objects.requireNonNull(value);
return map.put(createStoreKey(key), value);
}
@Override
public V remove(Object key) {
return map.remove(createLookupKey(key));
}
@Override
public void clear() {
map.clear();
// maintain() clears the queue, though it is not 100% reliable,
// as queue is populated by GC asynchronously
maintain();
}
@Override
public V putIfAbsent(K key, V value) {
Objects.requireNonNull(value);
Key storeKey = createStoreKey(key);
V oldValue = map.putIfAbsent(storeKey, value);
if (oldValue != null) { // ack that key has not been stored
storeKey.ackDeath();
}
return oldValue;
}
@Override
public boolean remove(Object key, Object value) {
if (value == null) {
return false;
}
return map.remove(createLookupKey(key), value);
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
Objects.requireNonNull(newValue);
return map.replace(createLookupKey(key), oldValue, newValue);
}
@Override
public V replace(K key, V value) {
Objects.requireNonNull(value);
return map.replace(createLookupKey(key), value);
}
@Override
public Collection<V> values() {
return map.values();
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
return new AbstractSet<Map.Entry<K, V>>() {
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public int size() {
return map.size();
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
private final Iterator<Map.Entry<Key, V>> it = map
.entrySet().iterator();
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Map.Entry<K, V> next() {
return new Map.Entry<K, V>() {
private final Map.Entry<Key, V> en = it.next();
@SuppressWarnings("unchecked")
@Override
public K getKey() {
return (K) en.getKey().get();
}
@Override
public V getValue() {
return en.getValue();
}
@Override
public V setValue(V value) {
Objects.requireNonNull(value);
return en.setValue(value);
}
};
}
@Override
public void remove() {
it.remove();
}
};
}
};
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.tomcat.util.collections;
/**
* This is intended as a (mostly) GC-free alternative to
* {@link java.util.concurrent.ConcurrentLinkedQueue} when the requirement is to
* create an unbounded queue with no requirement to shrink the queue. The aim is
* to provide the bare minimum of required functionality as quickly as possible
* with minimum garbage.
*
* @param <T> The type of object managed by this queue
*/
public class SynchronizedQueue<T> {
public static final int DEFAULT_SIZE = 128;
private Object[] queue;
private int size;
private int insert = 0;
private int remove = 0;
public SynchronizedQueue() {
this(DEFAULT_SIZE);
}
public SynchronizedQueue(int initialSize) {
queue = new Object[initialSize];
size = initialSize;
}
public synchronized boolean offer(T t) {
queue[insert++] = t;
// Wrap
if (insert == size) {
insert = 0;
}
if (insert == remove) {
expand();
}
return true;
}
public synchronized T poll() {
if (insert == remove) {
// empty
return null;
}
@SuppressWarnings("unchecked")
T result = (T) queue[remove];
queue[remove] = null;
remove++;
// Wrap
if (remove == size) {
remove = 0;
}
return result;
}
private void expand() {
int newSize = size * 2;
Object[] newQueue = new Object[newSize];
System.arraycopy(queue, insert, newQueue, 0, size - insert);
System.arraycopy(queue, 0, newQueue, size - insert, insert);
insert = size;
remove = 0;
queue = newQueue;
size = newSize;
}
public synchronized int size() {
int result = insert - remove;
if (result < 0) {
result += size;
}
return result;
}
public synchronized void clear() {
queue = new Object[size];
insert = 0;
remove = 0;
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.tomcat.util.collections;
/**
* This is intended as a (mostly) GC-free alternative to
* {@link java.util.concurrent.ConcurrentLinkedQueue} when the requirement is to
* create a pool of re-usable objects with no requirement to shrink the pool.
* The aim is to provide the bare minimum of required functionality as quickly
* as possible with minimum garbage.
*
* @param <T> The type of object managed by this stack
*/
public class SynchronizedStack<T> {
public static final int DEFAULT_SIZE = 128;
private static final int DEFAULT_LIMIT = -1;
private int size;
private final int limit;
/*
* Points to the next available object in the stack
*/
private int index = -1;
private Object[] stack;
public SynchronizedStack() {
this(DEFAULT_SIZE, DEFAULT_LIMIT);
}
public SynchronizedStack(int size, int limit) {
if (limit > -1 && size > limit) {
this.size = limit;
} else {
this.size = size;
}
this.limit = limit;
stack = new Object[size];
}
public synchronized boolean push(T obj) {
index++;
if (index == size) {
if (limit == -1 || size < limit) {
expand();
} else {
index--;
return false;
}
}
stack[index] = obj;
return true;
}
@SuppressWarnings("unchecked")
public synchronized T pop() {
if (index == -1) {
return null;
}
T result = (T) stack[index];
stack[index--] = null;
return result;
}
public synchronized void clear() {
if (index > -1) {
for (int i = 0; i < index + 1; i++) {
stack[i] = null;
}
}
index = -1;
}
private void expand() {
int newSize = size * 2;
if (limit != -1 && newSize > limit) {
newSize = limit;
}
Object[] newStack = new Object[newSize];
System.arraycopy(stack, 0, newStack, 0, size);
// This is the only point where garbage is created by throwing away the
// old array. Note it is only the array, not the contents, that becomes
// garbage.
stack = newStack;
size = newSize;
}
}

View File

@@ -0,0 +1,114 @@
/*
* 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.tomcat.util.compat;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.KeyStore.LoadStoreParameter;
import java.util.Collections;
import java.util.Map;
import javax.net.ssl.SSLParameters;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
class Jre8Compat extends JreCompat {
private static final Log log = LogFactory.getLog(Jre8Compat.class);
private static final StringManager sm = StringManager.getManager(Jre8Compat.class);
private static final int RUNTIME_MAJOR_VERSION = 8;
private static final Method setUseCipherSuitesOrderMethod;
private static final Constructor<?> domainLoadStoreParameterConstructor;
static {
Method m1 = null;
Constructor<?> c2 = null;
try {
// Order is important for the error handling below.
// Must look up m1 first.
// The class is Java6+...
Class<?> clazz1 = Class.forName("javax.net.ssl.SSLParameters");
// ...but this method is Java8+
m1 = clazz1.getMethod("setUseCipherSuitesOrder", boolean.class);
Class<?> clazz2 = Class.forName("java.security.DomainLoadStoreParameter");
c2 = clazz2.getConstructor(URI.class, Map.class);
} catch (SecurityException e) {
// Should never happen
log.error(sm.getString("jre8Compat.unexpected"), e);
} catch (NoSuchMethodException e) {
if (m1 == null) {
// Must be pre-Java 8
log.debug(sm.getString("jre8Compat.javaPre8"), e);
} else {
// Should never happen - signature error in lookup?
log.error(sm.getString("jre8Compat.unexpected"), e);
}
} catch (ClassNotFoundException e) {
// Should never happen
log.error(sm.getString("jre8Compat.unexpected"), e);
}
setUseCipherSuitesOrderMethod = m1;
domainLoadStoreParameterConstructor = c2;
}
static boolean isSupported() {
return setUseCipherSuitesOrderMethod != null;
}
@Override
public void setUseServerCipherSuitesOrder(SSLParameters sslParameters,
boolean useCipherSuitesOrder) {
try {
setUseCipherSuitesOrderMethod.invoke(sslParameters,
Boolean.valueOf(useCipherSuitesOrder));
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException(e);
} catch (IllegalAccessException e) {
throw new UnsupportedOperationException(e);
} catch (InvocationTargetException e) {
throw new UnsupportedOperationException(e);
}
}
@Override
public LoadStoreParameter getDomainLoadStoreParameter(URI uri) {
try {
return (LoadStoreParameter) domainLoadStoreParameterConstructor.newInstance(
uri, Collections.EMPTY_MAP);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
throw new UnsupportedOperationException(e);
}
}
@Override
public int jarFileRuntimeMajorVersion() {
return RUNTIME_MAJOR_VERSION;
}
}

View File

@@ -0,0 +1,277 @@
/*
* 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.tomcat.util.compat;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.Deque;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
class Jre9Compat extends Jre8Compat {
private static final Log log = LogFactory.getLog(Jre9Compat.class);
private static final StringManager sm = StringManager.getManager(Jre9Compat.class);
private static final Class<?> inaccessibleObjectExceptionClazz;
private static final Method setApplicationProtocolsMethod;
private static final Method getApplicationProtocolMethod;
private static final Method setDefaultUseCachesMethod;
private static final Method bootMethod;
private static final Method configurationMethod;
private static final Method modulesMethod;
private static final Method referenceMethod;
private static final Method locationMethod;
private static final Method isPresentMethod;
private static final Method getMethod;
private static final Constructor<JarFile> jarFileConstructor;
private static final Method isMultiReleaseMethod;
private static final Object RUNTIME_VERSION;
private static final int RUNTIME_MAJOR_VERSION;
private static final Method canAccessMethod;
private static final Method getModuleMethod;
private static final Method isExportedMethod;
static {
Class<?> c1 = null;
Method m2 = null;
Method m3 = null;
Method m4 = null;
Method m5 = null;
Method m6 = null;
Method m7 = null;
Method m8 = null;
Method m9 = null;
Method m10 = null;
Method m11 = null;
Constructor<JarFile> c12 = null;
Method m13 = null;
Object o14 = null;
Object o15 = null;
Method m16 = null;
Method m17 = null;
Method m18 = null;
try {
// Order is important for the error handling below.
// Must look up c1 first.
c1 = Class.forName("java.lang.reflect.InaccessibleObjectException");
Class<?> moduleLayerClazz = Class.forName("java.lang.ModuleLayer");
Class<?> configurationClazz = Class.forName("java.lang.module.Configuration");
Class<?> resolvedModuleClazz = Class.forName("java.lang.module.ResolvedModule");
Class<?> moduleReferenceClazz = Class.forName("java.lang.module.ModuleReference");
Class<?> optionalClazz = Class.forName("java.util.Optional");
Class<?> versionClazz = Class.forName("java.lang.Runtime$Version");
Method runtimeVersionMethod = JarFile.class.getMethod("runtimeVersion");
Method majorMethod = versionClazz.getMethod("major");
m2 = SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
m3 = SSLEngine.class.getMethod("getApplicationProtocol");
m4 = URLConnection.class.getMethod("setDefaultUseCaches", String.class, boolean.class);
m5 = moduleLayerClazz.getMethod("boot");
m6 = moduleLayerClazz.getMethod("configuration");
m7 = configurationClazz.getMethod("modules");
m8 = resolvedModuleClazz.getMethod("reference");
m9 = moduleReferenceClazz.getMethod("location");
m10 = optionalClazz.getMethod("isPresent");
m11 = optionalClazz.getMethod("get");
c12 = JarFile.class.getConstructor(File.class, boolean.class, int.class, versionClazz);
m13 = JarFile.class.getMethod("isMultiRelease");
o14 = runtimeVersionMethod.invoke(null);
o15 = majorMethod.invoke(o14);
m16 = AccessibleObject.class.getMethod("canAccess", new Class<?>[] { Object.class });
m17 = Class.class.getMethod("getModule");
Class<?> moduleClass = Class.forName("java.lang.Module");
m18 = moduleClass.getMethod("isExported", String.class);
} catch (ClassNotFoundException e) {
if (c1 == null) {
// Must be pre-Java 9
log.debug(sm.getString("jre9Compat.javaPre9"), e);
} else {
// Should never happen - signature error in lookup?
log.error(sm.getString("jre9Compat.unexpected"), e);
}
} catch (ReflectiveOperationException | IllegalArgumentException e) {
// Should never happen
log.error(sm.getString("jre9Compat.unexpected"), e);
}
inaccessibleObjectExceptionClazz = c1;
setApplicationProtocolsMethod = m2;
getApplicationProtocolMethod = m3;
setDefaultUseCachesMethod = m4;
bootMethod = m5;
configurationMethod = m6;
modulesMethod = m7;
referenceMethod = m8;
locationMethod = m9;
isPresentMethod = m10;
getMethod = m11;
jarFileConstructor = c12;
isMultiReleaseMethod = m13;
RUNTIME_VERSION = o14;
if (o15 != null) {
RUNTIME_MAJOR_VERSION = ((Integer) o15).intValue();
} else {
// Must be Java 8
RUNTIME_MAJOR_VERSION = 8;
}
canAccessMethod = m16;
getModuleMethod = m17;
isExportedMethod = m18;
}
static boolean isSupported() {
return inaccessibleObjectExceptionClazz != null;
}
@Override
public boolean isInstanceOfInaccessibleObjectException(Throwable t) {
if (t == null) {
return false;
}
return inaccessibleObjectExceptionClazz.isAssignableFrom(t.getClass());
}
@Override
public void setApplicationProtocols(SSLParameters sslParameters, String[] protocols) {
try {
setApplicationProtocolsMethod.invoke(sslParameters, (Object) protocols);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new UnsupportedOperationException(e);
}
}
@Override
public String getApplicationProtocol(SSLEngine sslEngine) {
try {
return (String) getApplicationProtocolMethod.invoke(sslEngine);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new UnsupportedOperationException(e);
}
}
@Override
public void disableCachingForJarUrlConnections() throws IOException {
try {
setDefaultUseCachesMethod.invoke(null, "JAR", Boolean.FALSE);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new UnsupportedOperationException(e);
}
}
@Override
public void addBootModulePath(Deque<URL> classPathUrlsToProcess) {
try {
Object bootLayer = bootMethod.invoke(null);
Object bootConfiguration = configurationMethod.invoke(bootLayer);
Set<?> resolvedModules = (Set<?>) modulesMethod.invoke(bootConfiguration);
for (Object resolvedModule : resolvedModules) {
Object moduleReference = referenceMethod.invoke(resolvedModule);
Object optionalURI = locationMethod.invoke(moduleReference);
Boolean isPresent = (Boolean) isPresentMethod.invoke(optionalURI);
if (isPresent.booleanValue()) {
URI uri = (URI) getMethod.invoke(optionalURI);
try {
URL url = uri.toURL();
classPathUrlsToProcess.add(url);
} catch (MalformedURLException e) {
log.warn(sm.getString("jre9Compat.invalidModuleUri", uri), e);
}
}
}
} catch (ReflectiveOperationException e) {
throw new UnsupportedOperationException(e);
}
}
@Override
public JarFile jarFileNewInstance(File f) throws IOException {
try {
return jarFileConstructor.newInstance(
f, Boolean.TRUE, Integer.valueOf(ZipFile.OPEN_READ), RUNTIME_VERSION);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new IOException(e);
}
}
@Override
public boolean jarFileIsMultiRelease(JarFile jarFile) {
try {
return ((Boolean) isMultiReleaseMethod.invoke(jarFile)).booleanValue();
} catch (ReflectiveOperationException | IllegalArgumentException e) {
return false;
}
}
@Override
public int jarFileRuntimeMajorVersion() {
return RUNTIME_MAJOR_VERSION;
}
@Override
public boolean canAcccess(Object base, AccessibleObject accessibleObject) {
try {
return ((Boolean) canAccessMethod.invoke(accessibleObject, base)).booleanValue();
} catch (ReflectiveOperationException | IllegalArgumentException e) {
return false;
}
}
@Override
public boolean isExported(Class<?> type) {
try {
String packageName = type.getPackage().getName();
Object module = getModuleMethod.invoke(type);
return ((Boolean) isExportedMethod.invoke(module, packageName)).booleanValue();
} catch (ReflectiveOperationException e) {
return false;
}
}
}

View File

@@ -0,0 +1,247 @@
/*
* 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.tomcat.util.compat;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyStore.LoadStoreParameter;
import java.util.Deque;
import java.util.jar.JarFile;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import org.apache.tomcat.util.res.StringManager;
/**
* This is the base implementation class for JRE compatibility and provides an
* implementation based on Java 7. Sub-classes may extend this class and provide
* alternative implementations for later JRE versions
*/
public class JreCompat {
private static final int RUNTIME_MAJOR_VERSION = 7;
private static final JreCompat instance;
private static StringManager sm =
StringManager.getManager(JreCompat.class.getPackage().getName());
private static final boolean jre9Available;
private static final boolean jre8Available;
static {
// This is Tomcat 8 with a minimum Java version of Java 7. The latest
// Java version the optional features require is Java 9.
// Look for the highest supported JVM first
if (Jre9Compat.isSupported()) {
instance = new Jre9Compat();
jre9Available = true;
jre8Available = true;
}
else if (Jre8Compat.isSupported()) {
instance = new Jre8Compat();
jre9Available = false;
jre8Available = true;
} else {
instance = new JreCompat();
jre9Available = false;
jre8Available = false;
}
}
public static JreCompat getInstance() {
return instance;
}
// Java 7 implementation of Java 8 methods
public static boolean isJre8Available() {
return jre8Available;
}
@SuppressWarnings("unused")
public void setUseServerCipherSuitesOrder(SSLParameters engine, boolean useCipherSuitesOrder) {
throw new UnsupportedOperationException(sm.getString("jreCompat.noServerCipherSuiteOrder"));
}
@SuppressWarnings("unused")
public LoadStoreParameter getDomainLoadStoreParameter(URI uri) {
throw new UnsupportedOperationException(sm.getString("jreCompat.noDomainLoadStoreParameter"));
}
// Java 7 implementation of Java 9 methods
public static boolean isJre9Available() {
return jre9Available;
}
/**
* Test if the provided exception is an instance of
* java.lang.reflect.InaccessibleObjectException.
*
* @param t The exception to test
*
* @return {@code true} if the exception is an instance of
* InaccessibleObjectException, otherwise {@code false}
*/
public boolean isInstanceOfInaccessibleObjectException(Throwable t) {
// Exception does not exist prior to Java 9
return false;
}
/**
* Set the application protocols the server will accept for ALPN
*
* @param sslParameters The SSL parameters for a connection
* @param protocols The application protocols to be allowed for that
* connection
*/
public void setApplicationProtocols(SSLParameters sslParameters, String[] protocols) {
throw new UnsupportedOperationException(sm.getString("jreCompat.noApplicationProtocols"));
}
/**
* Get the application protocol that has been negotiated for connection
* associated with the given SSLEngine.
*
* @param sslEngine The SSLEngine for which to obtain the negotiated
* protocol
*
* @return The name of the negotiated protocol
*/
public String getApplicationProtocol(SSLEngine sslEngine) {
throw new UnsupportedOperationException(sm.getString("jreCompat.noApplicationProtocol"));
}
/**
* Disables caching for JAR URL connections. For Java 8 and earlier, this also disables
* caching for ALL URL connections.
*
* @throws IOException If a dummy JAR URLConnection can not be created
*/
public void disableCachingForJarUrlConnections() throws IOException {
// Doesn't matter that this JAR doesn't exist - just as
// long as the URL is well-formed
URL url = new URL("jar:file://dummy.jar!/");
URLConnection uConn = url.openConnection();
uConn.setDefaultUseCaches(false);
}
/**
* Obtains the URLs for all the JARs on the module path when the JVM starts
* and adds them to the provided Deque.
*
* @param classPathUrlsToProcess The Deque to which the modules should be
* added
*/
public void addBootModulePath(Deque<URL> classPathUrlsToProcess) {
// NO-OP for Java 7. There is no module path.
}
/**
* Creates a new JarFile instance. When running on Java 9 and later, the
* JarFile will be multi-release JAR aware. While this isn't strictly
* required to be in this package, it is provided as a convenience method.
*
* @param s The JAR file to open
*
* @return A JarFile instance based on the provided path
*
* @throws IOException If an I/O error occurs creating the JarFile instance
*/
public final JarFile jarFileNewInstance(String s) throws IOException {
return jarFileNewInstance(new File(s));
}
/**
* Creates a new JarFile instance. When running on Java 9 and later, the
* JarFile will be multi-release JAR aware.
*
* @param f The JAR file to open
*
* @return A JarFile instance based on the provided file
*
* @throws IOException If an I/O error occurs creating the JarFile instance
*/
public JarFile jarFileNewInstance(File f) throws IOException {
return new JarFile(f);
}
/**
* Is this JarFile a multi-release JAR file.
*
* @param jarFile The JarFile to test
*
* @return {@code true} If it is a multi-release JAR file and is configured
* to behave as such.
*/
public boolean jarFileIsMultiRelease(JarFile jarFile) {
// Java 8 doesn't support multi-release so default to false
return false;
}
public int jarFileRuntimeMajorVersion() {
return RUNTIME_MAJOR_VERSION;
}
/**
* Is the accessibleObject accessible (as a result of appropriate module
* exports) on the provided instance?
*
* @param base The specific instance to be tested.
* @param accessibleObject The method/field/constructor to be tested.
*
* @return {code true} if the AccessibleObject can be accessed otherwise
* {code false}
*/
public boolean canAcccess(Object base, AccessibleObject accessibleObject) {
// Java 8 doesn't support modules so default to true
return true;
}
/**
* Is the given class in an exported package?
*
* @param type The class to test
*
* @return Always {@code true} for Java 8. {@code true} if the enclosing
* package is exported for Java 9+
*/
public boolean isExported(Class<?> type) {
return true;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.tomcat.util.compat;
import java.security.AccessController;
import java.security.PrivilegedAction;
public class JrePlatform {
private static final String OS_NAME_PROPERTY = "os.name";
private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
static {
/*
* There are a few places where a) the behaviour of the Java API depends
* on the underlying platform and b) those behavioural differences have
* an impact on Tomcat.
*
* Tomcat therefore needs to be able to determine the platform it is
* running on to account for those differences.
*
* In an ideal world this code would not exist.
*/
// This check is derived from the check in Apache Commons Lang
String osName;
if (System.getSecurityManager() == null) {
osName = System.getProperty(OS_NAME_PROPERTY);
} else {
osName = AccessController.doPrivileged(
new PrivilegedAction<String>() {
@Override
public String run() {
return System.getProperty(OS_NAME_PROPERTY);
}
});
}
IS_WINDOWS = osName.startsWith(OS_NAME_WINDOWS_PREFIX);
}
public static final boolean IS_WINDOWS;
}

View File

@@ -0,0 +1,49 @@
/*
* 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.tomcat.util.compat;
import java.util.Locale;
public class JreVendor {
static {
/**
* There are a few places where Tomcat either accesses JVM internals
* (e.g. the memory leak protection) or where feature support varies
* between JVMs (e.g. SPNEGO). These flags exist to enable Tomcat to
* adjust its behaviour based on the vendor of the JVM. In an ideal
* world this code would not exist.
*/
String vendor = System.getProperty("java.vendor", "");
vendor = vendor.toLowerCase(Locale.ENGLISH);
if (vendor.startsWith("oracle") || vendor.startsWith("sun")) {
IS_ORACLE_JVM = true;
IS_IBM_JVM = false;
} else if (vendor.contains("ibm")) {
IS_ORACLE_JVM = false;
IS_IBM_JVM = true;
} else {
IS_ORACLE_JVM = false;
IS_IBM_JVM = false;
}
}
public static final boolean IS_ORACLE_JVM;
public static final boolean IS_IBM_JVM;
}

View File

@@ -0,0 +1,26 @@
# 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.
jre8Compat.javaPre8=Class not found so assuming code is running on a pre-Java 8 JVM
jre8Compat.unexpected=Failed to create references to Java 8 classes and methods
jre9Compat.invalidModuleUri=The module URI provided [{0}] could not be converted to a URL for the JarScanner to process
jre9Compat.javaPre9=Class not found so assuming code is running on a pre-Java 9 JVM
jre9Compat.unexpected=Failed to create references to Java 9 classes and methods
jreCompat.noApplicationProtocol=Java Runtime does not support SSLEngine.getApplicationProtocol(). You must use Java 9 to use this feature.
jreCompat.noApplicationProtocols=Java Runtime does not support SSLParameters.setApplicationProtocols(). You must use Java 9 to use this feature.
jreCompat.noDomainLoadStoreParameter=Java Runtime does not support DKS key store type. You must use Java 8 or later to use this feature.
jreCompat.noServerCipherSuiteOrder=Java Runtime does not support "useServerCipherSuitesOrder". You must use Java 8 or later to use this feature.

View File

@@ -0,0 +1,16 @@
# 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.
jreCompat.noApplicationProtocol=Die Java Runtime unterstützt nicht den SSLEngine.getApplicationProtocol() Aufruf. Um dieses Feature nutzen zu können muss Java 9 verwendet werden.

View File

@@ -0,0 +1,18 @@
# 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.
jre9Compat.invalidModuleUri=El módulo URI proveído [{0}] no pudo ser convertiod a una URL para ser procesado por JarScanner
jreCompat.noApplicationProtocol=Java Runtime no soporta SSLEngine.getApplicationProtocol(). Se necesita Java 9 para usar esta función.

View File

@@ -0,0 +1,21 @@
# 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.
jre9Compat.invalidModuleUri=L''URI du module fournie [{0}] n''a pas pu être convertie en URL pour être traitée par le JarScanner
jre9Compat.javaPre9=Le code est considéré être exécuté sur une JVM antérieure à Java 9 car la classe n'a pas été trouvée
jre9Compat.unexpected=Impossible de créer les références vers les classes et méthodes de Java 9
jreCompat.noApplicationProtocol=Le Java Runtime utilisé ne supporte pas SSLEngine.getApplicationProtocol(). Il faut Java 9 pour utiliser cette option.
jreCompat.noApplicationProtocols=L'environnement Java ne supporte pas SSLParameters.setApplicationProtocols(), cette fonctionnalité demande Java 9

View File

@@ -0,0 +1,21 @@
# 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.
jre9Compat.invalidModuleUri=モジュール URI [{0}] を JarScanner で処理する URL に変換できませんでした。
jreCompat.noApplicationProtocol=Java 実行環境が SSLEngine.getApplicationProtocol() に対応していません。Java 9 以降で実行する必要があります。
jreCompat.noApplicationProtocols=\n\
113/5000\n\
Java RuntimeはSSLParameters.setApplicationProtocols()をサポートしていません。 この機能を使用するには、Java 9を使用する必要があります。

View File

@@ -0,0 +1,21 @@
# 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.
jre9Compat.invalidModuleUri=[{0}](으)로 제공된 모듈 URI는 JarScanner가 처리할 수 있는 URL로 변환될 수 없습니다.
jre9Compat.javaPre9=Java 9 클래스가 발견되지 않습니다. Java 9 이전 버전에서 동작하고 있는 것으로 보입니다.
jre9Compat.unexpected=Java 9 클래스들과 메소드들을 참조할 수 없습니다.
jreCompat.noApplicationProtocol=자바 런타임이 SSLEngine.getApplicationProtocol()을 지원하지 않습니다. 이 기능을 사용하려면 Java 9를 사용해야 합니다.
jreCompat.noApplicationProtocols=자바 런타임이 SSLParameters.setApplicationProtocols()을 지원하지 않습니다. 이 기능을 사용하려면 Java 9를 사용해야 합니다.

View File

@@ -0,0 +1,20 @@
# 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.
jre9Compat.invalidModuleUri=提供的模块URI [{0}]无法转换为JarScanner要处理的URL
jre9Compat.javaPre9=类未找到所以假设代码运行在pre-Java 8虚拟机上
jre9Compat.unexpected=创建对Java 9类的依赖和方法失败
jreCompat.noApplicationProtocol=Java 运行时不支持 SSLEngine.getApplicationProtocol()。要使用该功能你必须使用 Java 9。

View File

@@ -0,0 +1,49 @@
/*
* 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.tomcat.util.compat;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import org.apache.tomcat.util.net.Constants;
/**
* This class checks for the availability of TLS features.
*
* @deprecated Unused. This will be removed in Tomcat 10.
*/
@Deprecated
public class TLS {
private static final boolean tlsv13Available;
static {
boolean ok = false;
try {
SSLContext.getInstance(Constants.SSL_PROTO_TLSv1_3);
ok = true;
} catch (NoSuchAlgorithmException ex) {
}
tlsv13Available = ok;
}
public static boolean isTlsv13Available() {
return tlsv13Available;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.tomcat.util.descriptor;
public class Constants {
public static final String PACKAGE_NAME =
Constants.class.getPackage().getName();
public static final boolean IS_SECURITY_ENABLED = (System.getSecurityManager() != null);
}

View File

@@ -0,0 +1,186 @@
/*
* 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.tomcat.util.descriptor;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.digester.Digester;
import org.apache.tomcat.util.digester.RuleSet;
import org.apache.tomcat.util.res.StringManager;
import org.xml.sax.ext.EntityResolver2;
/**
* Wrapper class around the Digester that hide Digester's initialization
* details.
*/
public class DigesterFactory {
private static final StringManager sm =
StringManager.getManager(Constants.PACKAGE_NAME);
private static final Class<ServletContext> CLASS_SERVLET_CONTEXT;
private static final Class<?> CLASS_JSP_CONTEXT;
static {
CLASS_SERVLET_CONTEXT = ServletContext.class;
Class<?> jspContext = null;
try {
jspContext = Class.forName("javax.servlet.jsp.JspContext");
} catch (ClassNotFoundException e) {
// Ignore - JSP API is not present.
}
CLASS_JSP_CONTEXT = jspContext;
}
/**
* Mapping of well-known public IDs used by the Servlet API to the matching
* local resource.
*/
public static final Map<String,String> SERVLET_API_PUBLIC_IDS;
/**
* Mapping of well-known system IDs used by the Servlet API to the matching
* local resource.
*/
public static final Map<String,String> SERVLET_API_SYSTEM_IDS;
static {
Map<String, String> publicIds = new HashMap<>();
Map<String, String> systemIds = new HashMap<>();
// W3C
add(publicIds, XmlIdentifiers.XSD_10_PUBLIC, locationFor("XMLSchema.dtd"));
add(publicIds, XmlIdentifiers.DATATYPES_PUBLIC, locationFor("datatypes.dtd"));
add(systemIds, XmlIdentifiers.XML_2001_XSD, locationFor("xml.xsd"));
// from J2EE 1.2
add(publicIds, XmlIdentifiers.WEB_22_PUBLIC, locationFor("web-app_2_2.dtd"));
add(publicIds, XmlIdentifiers.TLD_11_PUBLIC, locationFor("web-jsptaglibrary_1_1.dtd"));
// from J2EE 1.3
add(publicIds, XmlIdentifiers.WEB_23_PUBLIC, locationFor("web-app_2_3.dtd"));
add(publicIds, XmlIdentifiers.TLD_12_PUBLIC, locationFor("web-jsptaglibrary_1_2.dtd"));
// from J2EE 1.4
add(systemIds, "http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd",
locationFor("j2ee_web_services_1_1.xsd"));
add(systemIds, "http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",
locationFor("j2ee_web_services_client_1_1.xsd"));
add(systemIds, XmlIdentifiers.WEB_24_XSD, locationFor("web-app_2_4.xsd"));
add(systemIds, XmlIdentifiers.TLD_20_XSD, locationFor("web-jsptaglibrary_2_0.xsd"));
addSelf(systemIds, "j2ee_1_4.xsd");
addSelf(systemIds, "jsp_2_0.xsd");
// from JavaEE 5
add(systemIds, XmlIdentifiers.WEB_25_XSD, locationFor("web-app_2_5.xsd"));
add(systemIds, XmlIdentifiers.TLD_21_XSD, locationFor("web-jsptaglibrary_2_1.xsd"));
addSelf(systemIds, "javaee_5.xsd");
addSelf(systemIds, "jsp_2_1.xsd");
addSelf(systemIds, "javaee_web_services_1_2.xsd");
addSelf(systemIds, "javaee_web_services_client_1_2.xsd");
// from JavaEE 6
add(systemIds, XmlIdentifiers.WEB_30_XSD, locationFor("web-app_3_0.xsd"));
add(systemIds, XmlIdentifiers.WEB_FRAGMENT_30_XSD, locationFor("web-fragment_3_0.xsd"));
addSelf(systemIds, "web-common_3_0.xsd");
addSelf(systemIds, "javaee_6.xsd");
addSelf(systemIds, "jsp_2_2.xsd");
addSelf(systemIds, "javaee_web_services_1_3.xsd");
addSelf(systemIds, "javaee_web_services_client_1_3.xsd");
// from JavaEE 7
add(systemIds, XmlIdentifiers.WEB_31_XSD, locationFor("web-app_3_1.xsd"));
add(systemIds, XmlIdentifiers.WEB_FRAGMENT_31_XSD, locationFor("web-fragment_3_1.xsd"));
addSelf(systemIds, "web-common_3_1.xsd");
addSelf(systemIds, "javaee_7.xsd");
addSelf(systemIds, "jsp_2_3.xsd");
addSelf(systemIds, "javaee_web_services_1_4.xsd");
addSelf(systemIds, "javaee_web_services_client_1_4.xsd");
SERVLET_API_PUBLIC_IDS = Collections.unmodifiableMap(publicIds);
SERVLET_API_SYSTEM_IDS = Collections.unmodifiableMap(systemIds);
}
private static void addSelf(Map<String, String> ids, String id) {
String location = locationFor(id);
if (location != null) {
ids.put(id, location);
ids.put(location, location);
}
}
private static void add(Map<String,String> ids, String id, String location) {
if (location != null) {
ids.put(id, location);
// BZ 63311
// Support http and https locations as the move away from http and
// towards https continues.
if (id.startsWith("http://")) {
String httpsId = "https://" + id.substring(7);
ids.put(httpsId, location);
}
}
}
private static String locationFor(String name) {
URL location = CLASS_SERVLET_CONTEXT.getResource("resources/" + name);
if (location == null && CLASS_JSP_CONTEXT != null) {
location = CLASS_JSP_CONTEXT.getResource("resources/" + name);
}
if (location == null) {
Log log = LogFactory.getLog(DigesterFactory.class);
log.warn(sm.getString("digesterFactory.missingSchema", name));
return null;
}
return location.toExternalForm();
}
/**
* Create a <code>Digester</code> parser.
* @param xmlValidation turn on/off xml validation
* @param xmlNamespaceAware turn on/off namespace validation
* @param rule an instance of <code>RuleSet</code> used for parsing the xml.
* @param blockExternal turn on/off the blocking of external resources
* @return a new digester
*/
public static Digester newDigester(boolean xmlValidation,
boolean xmlNamespaceAware,
RuleSet rule,
boolean blockExternal) {
Digester digester = new Digester();
digester.setNamespaceAware(xmlNamespaceAware);
digester.setValidating(xmlValidation);
digester.setUseContextClassLoader(true);
EntityResolver2 resolver = new LocalResolver(SERVLET_API_PUBLIC_IDS,
SERVLET_API_SYSTEM_IDS, blockExternal);
digester.setEntityResolver(resolver);
if (rule != null) {
digester.addRuleSet(rule);
}
return digester;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.tomcat.util.descriptor;
import java.io.InputStream;
import org.apache.tomcat.util.ExceptionUtils;
import org.xml.sax.InputSource;
public final class InputSourceUtil {
private InputSourceUtil() {
// Utility class. Hide default constructor.
}
public static void close(InputSource inputSource) {
if (inputSource == null) {
// Nothing to do
return;
}
InputStream is = inputSource.getByteStream();
if (is != null) {
try {
is.close();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More