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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,704 @@
/*
* 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.catalina.valves;
import java.io.BufferedWriter;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.catalina.LifecycleException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.B2CConverter;
/**
* This is a concrete implementation of {@link AbstractAccessLogValve} that
* outputs the access log to a file. The features of this implementation
* include:
* <ul>
* <li>Automatic date-based rollover of log files</li>
* <li>Optional log file rotation</li>
* </ul>
* <p>
* For UNIX users, another field called <code>checkExists</code> is also
* available. If set to true, the log file's existence will be checked before
* each logging. This way an external log rotator can move the file
* somewhere and Tomcat will start with a new file.
* </p>
*
* <p>
* For JMX junkies, a public method called <code>rotate</code> has
* been made available to allow you to tell this instance to move
* the existing log file to somewhere else and start writing a new log file.
* </p>
*/
public class AccessLogValve extends AbstractAccessLogValve {
private static final Log log = LogFactory.getLog(AccessLogValve.class);
//------------------------------------------------------ Constructor
public AccessLogValve() {
super();
}
// ----------------------------------------------------- Instance Variables
/**
* The as-of date for the currently open log file, or a zero-length
* string if there is no open log file.
*/
private volatile String dateStamp = "";
/**
* The directory in which log files are created.
*/
private String directory = "logs";
/**
* The prefix that is added to log file filenames.
*/
protected volatile String prefix = "access_log";
/**
* Should we rotate our log file? Default is true (like old behavior)
*/
protected boolean rotatable = true;
/**
* Should we defer inclusion of the date stamp in the file
* name until rotate time? Default is false.
*/
protected boolean renameOnRotate = false;
/**
* Buffered logging.
*/
private boolean buffered = true;
/**
* The suffix that is added to log file filenames.
*/
protected volatile String suffix = "";
/**
* The PrintWriter to which we are currently logging, if any.
*/
protected PrintWriter writer = null;
/**
* A date formatter to format a Date using the format
* given by <code>fileDateFormat</code>.
*/
protected SimpleDateFormat fileDateFormatter = null;
/**
* The current log file we are writing to. Helpful when checkExists
* is true.
*/
protected File currentLogFile = null;
/**
* Instant when the log daily rotation was last checked.
*/
private volatile long rotationLastChecked = 0L;
/**
* Do we check for log file existence? Helpful if an external
* agent renames the log file so we can automagically recreate it.
*/
private boolean checkExists = false;
/**
* Date format to place in log file name.
*/
protected String fileDateFormat = ".yyyy-MM-dd";
/**
* Character set used by the log file. If it is <code>null</code>, the
* system default character set will be used. An empty string will be
* treated as <code>null</code> when this property is assigned.
*/
protected volatile String encoding = null;
/**
* The number of days to retain the access log files before they are
* removed.
*/
private int maxDays = -1;
private volatile boolean checkForOldLogs = false;
// ------------------------------------------------------------- Properties
public int getMaxDays() {
return maxDays;
}
public void setMaxDays(int maxDays) {
this.maxDays = maxDays;
}
/**
* @return the directory in which we create log files.
*/
public String getDirectory() {
return directory;
}
/**
* Set the directory in which we create log files.
*
* @param directory The new log file directory
*/
public void setDirectory(String directory) {
this.directory = directory;
}
/**
* Check for file existence before logging.
* @return <code>true</code> if file existence is checked first
*/
public boolean isCheckExists() {
return checkExists;
}
/**
* Set whether to check for log file existence before logging.
*
* @param checkExists true meaning to check for file existence.
*/
public void setCheckExists(boolean checkExists) {
this.checkExists = checkExists;
}
/**
* @return the log file prefix.
*/
public String getPrefix() {
return prefix;
}
/**
* Set the log file prefix.
*
* @param prefix The new log file prefix
*/
public void setPrefix(String prefix) {
this.prefix = prefix;
}
/**
* Should we rotate the access log.
*
* @return <code>true</code> if the access log should be rotated
*/
public boolean isRotatable() {
return rotatable;
}
/**
* Configure whether the access log should be rotated.
*
* @param rotatable true if the log should be rotated
*/
public void setRotatable(boolean rotatable) {
this.rotatable = rotatable;
}
/**
* Should we defer inclusion of the date stamp in the file
* name until rotate time.
* @return <code>true</code> if the logs file names are time stamped
* only when they are rotated
*/
public boolean isRenameOnRotate() {
return renameOnRotate;
}
/**
* Set the value if we should defer inclusion of the date
* stamp in the file name until rotate time
*
* @param renameOnRotate true if defer inclusion of date stamp
*/
public void setRenameOnRotate(boolean renameOnRotate) {
this.renameOnRotate = renameOnRotate;
}
/**
* Is the logging buffered. Usually buffering can increase performance.
* @return <code>true</code> if the logging uses a buffer
*/
public boolean isBuffered() {
return buffered;
}
/**
* Set the value if the logging should be buffered
*
* @param buffered <code>true</code> if buffered.
*/
public void setBuffered(boolean buffered) {
this.buffered = buffered;
}
/**
* @return the log file suffix.
*/
public String getSuffix() {
return suffix;
}
/**
* Set the log file suffix.
*
* @param suffix The new log file suffix
*/
public void setSuffix(String suffix) {
this.suffix = suffix;
}
/**
* @return the date format date based log rotation.
*/
public String getFileDateFormat() {
return fileDateFormat;
}
/**
* Set the date format date based log rotation.
* @param fileDateFormat The format for the file timestamp
*/
public void setFileDateFormat(String fileDateFormat) {
String newFormat;
if (fileDateFormat == null) {
newFormat = "";
} else {
newFormat = fileDateFormat;
}
this.fileDateFormat = newFormat;
synchronized (this) {
fileDateFormatter = new SimpleDateFormat(newFormat, Locale.US);
fileDateFormatter.setTimeZone(TimeZone.getDefault());
}
}
/**
* Return the character set name that is used to write the log file.
*
* @return Character set name, or <code>null</code> if the system default
* character set is used.
*/
public String getEncoding() {
return encoding;
}
/**
* Set the character set that is used to write the log file.
*
* @param encoding The name of the character set.
*/
public void setEncoding(String encoding) {
if (encoding != null && encoding.length() > 0) {
this.encoding = encoding;
} else {
this.encoding = null;
}
}
// --------------------------------------------------------- Public Methods
/**
* Execute a periodic task, such as reloading, etc. This method will be
* invoked inside the classloading context of this container. Unexpected
* throwables will be caught and logged.
*/
@Override
public synchronized void backgroundProcess() {
if (getState().isAvailable() && getEnabled() && writer != null &&
buffered) {
writer.flush();
}
int maxDays = this.maxDays;
String prefix = this.prefix;
String suffix = this.suffix;
if (rotatable && checkForOldLogs && maxDays > 0) {
long deleteIfLastModifiedBefore =
System.currentTimeMillis() - (maxDays * 24L * 60 * 60 * 1000);
File dir = getDirectoryFile();
if (dir.isDirectory()) {
String[] oldAccessLogs = dir.list();
if (oldAccessLogs != null) {
for (String oldAccessLog : oldAccessLogs) {
boolean match = false;
if (prefix != null && prefix.length() > 0) {
if (!oldAccessLog.startsWith(prefix)) {
continue;
}
match = true;
}
if (suffix != null && suffix.length() > 0) {
if (!oldAccessLog.endsWith(suffix)) {
continue;
}
match = true;
}
if (match) {
File file = new File(dir, oldAccessLog);
if (file.isFile() && file.lastModified() < deleteIfLastModifiedBefore) {
if (!file.delete()) {
log.warn(sm.getString(
"accessLogValve.deleteFail", file.getAbsolutePath()));
}
}
}
}
}
}
checkForOldLogs = false;
}
}
/**
* Rotate the log file if necessary.
*/
public void rotate() {
if (rotatable) {
// Only do a logfile switch check once a second, max.
long systime = System.currentTimeMillis();
if ((systime - rotationLastChecked) > 1000) {
synchronized(this) {
if ((systime - rotationLastChecked) > 1000) {
rotationLastChecked = systime;
String tsDate;
// Check for a change of date
tsDate = fileDateFormatter.format(new Date(systime));
// If the date has changed, switch log files
if (!dateStamp.equals(tsDate)) {
close(true);
dateStamp = tsDate;
open();
}
}
}
}
}
}
/**
* Rename the existing log file to something else. Then open the
* old log file name up once again. Intended to be called by a JMX
* agent.
*
* @param newFileName The file name to move the log file entry to
* @return true if a file was rotated with no error
*/
public synchronized boolean rotate(String newFileName) {
if (currentLogFile != null) {
File holder = currentLogFile;
close(false);
try {
holder.renameTo(new File(newFileName));
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
log.error(sm.getString("accessLogValve.rotateFail"), e);
}
/* Make sure date is correct */
dateStamp = fileDateFormatter.format(
new Date(System.currentTimeMillis()));
open();
return true;
} else {
return false;
}
}
// -------------------------------------------------------- Private Methods
private File getDirectoryFile() {
File dir = new File(directory);
if (!dir.isAbsolute()) {
dir = new File(getContainer().getCatalinaBase(), directory);
}
return dir;
}
/**
* Create a File object based on the current log file name.
* Directories are created as needed but the underlying file
* is not created or opened.
*
* @param useDateStamp include the timestamp in the file name.
* @return the log file object
*/
private File getLogFile(boolean useDateStamp) {
// Create the directory if necessary
File dir = getDirectoryFile();
if (!dir.mkdirs() && !dir.isDirectory()) {
log.error(sm.getString("accessLogValve.openDirFail", dir));
}
// Calculate the current log file name
File pathname;
if (useDateStamp) {
pathname = new File(dir.getAbsoluteFile(), prefix + dateStamp
+ suffix);
} else {
pathname = new File(dir.getAbsoluteFile(), prefix + suffix);
}
File parent = pathname.getParentFile();
if (!parent.mkdirs() && !parent.isDirectory()) {
log.error(sm.getString("accessLogValve.openDirFail", parent));
}
return pathname;
}
/**
* Move a current but rotated log file back to the unrotated
* one. Needed if date stamp inclusion is deferred to rotation
* time.
*/
private void restore() {
File newLogFile = getLogFile(false);
File rotatedLogFile = getLogFile(true);
if (rotatedLogFile.exists() && !newLogFile.exists() &&
!rotatedLogFile.equals(newLogFile)) {
try {
if (!rotatedLogFile.renameTo(newLogFile)) {
log.error(sm.getString("accessLogValve.renameFail", rotatedLogFile, newLogFile));
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
log.error(sm.getString("accessLogValve.renameFail", rotatedLogFile, newLogFile), e);
}
}
}
/**
* Close the currently open log file (if any)
*
* @param rename Rename file to final name after closing
*/
private synchronized void close(boolean rename) {
if (writer == null) {
return;
}
writer.flush();
writer.close();
if (rename && renameOnRotate) {
File newLogFile = getLogFile(true);
if (!newLogFile.exists()) {
try {
if (!currentLogFile.renameTo(newLogFile)) {
log.error(sm.getString("accessLogValve.renameFail", currentLogFile, newLogFile));
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
log.error(sm.getString("accessLogValve.renameFail", currentLogFile, newLogFile), e);
}
} else {
log.error(sm.getString("accessLogValve.alreadyExists", currentLogFile, newLogFile));
}
}
writer = null;
dateStamp = "";
currentLogFile = null;
}
/**
* Log the specified message to the log file, switching files if the date
* has changed since the previous log call.
*
* @param message Message to be logged
*/
@Override
public void log(CharArrayWriter message) {
rotate();
/* In case something external rotated the file instead */
if (checkExists) {
synchronized (this) {
if (currentLogFile != null && !currentLogFile.exists()) {
try {
close(false);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
log.info(sm.getString("accessLogValve.closeFail"), e);
}
/* Make sure date is correct */
dateStamp = fileDateFormatter.format(
new Date(System.currentTimeMillis()));
open();
}
}
}
// Log this message
try {
message.write(System.lineSeparator());
synchronized(this) {
if (writer != null) {
message.writeTo(writer);
if (!buffered) {
writer.flush();
}
}
}
} catch (IOException ioe) {
log.warn(sm.getString(
"accessLogValve.writeFail", message.toString()), ioe);
}
}
/**
* Open the new log file for the date specified by <code>dateStamp</code>.
*/
protected synchronized void open() {
// Open the current log file
// If no rotate - no need for dateStamp in fileName
File pathname = getLogFile(rotatable && !renameOnRotate);
Charset charset = null;
if (encoding != null) {
try {
charset = B2CConverter.getCharset(encoding);
} catch (UnsupportedEncodingException ex) {
log.error(sm.getString(
"accessLogValve.unsupportedEncoding", encoding), ex);
}
}
if (charset == null) {
charset = StandardCharsets.ISO_8859_1;
}
try {
writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(pathname, true), charset), 128000),
false);
currentLogFile = pathname;
} catch (IOException e) {
writer = null;
currentLogFile = null;
log.error(sm.getString("accessLogValve.openFail", pathname), e);
}
// Rotating a log file will always trigger a new file to be opened so
// when a new file is opened, check to see if any old files need to be
// removed.
checkForOldLogs = true;
}
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Initialize the Date formatters
String format = getFileDateFormat();
fileDateFormatter = new SimpleDateFormat(format, Locale.US);
fileDateFormatter.setTimeZone(TimeZone.getDefault());
dateStamp = fileDateFormatter.format(new Date(System.currentTimeMillis()));
if (rotatable && renameOnRotate) {
restore();
}
open();
super.startInternal();
}
/**
* Stop this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void stopInternal() throws LifecycleException {
super.stopInternal();
close(false);
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.catalina.valves;
/**
* Manifest constants for the <code>org.apache.catalina.valves</code>
* package.
*
* @author Craig R. McClanahan
*/
public final class Constants {
public static final String Package = "org.apache.catalina.valves";
// Constants for the AccessLogValve class
public static final class AccessLog {
public static final String COMMON_ALIAS = "common";
public static final String COMMON_PATTERN = "%h %l %u %t \"%r\" %s %b";
public static final String COMBINED_ALIAS = "combined";
public static final String COMBINED_PATTERN = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"";
}
}

View File

@@ -0,0 +1,297 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Web crawlers can trigger the creation of many thousands of sessions as they
* crawl a site which may result in significant memory consumption. This Valve
* ensures that crawlers are associated with a single session - just like normal
* users - regardless of whether or not they provide a session token with their
* requests.
*/
public class CrawlerSessionManagerValve extends ValveBase {
private static final Log log = LogFactory.getLog(CrawlerSessionManagerValve.class);
private final Map<String, String> clientIdSessionId = new ConcurrentHashMap<>();
private final Map<String, String> sessionIdClientId = new ConcurrentHashMap<>();
private String crawlerUserAgents = ".*[bB]ot.*|.*Yahoo! Slurp.*|.*Feedfetcher-Google.*";
private Pattern uaPattern = null;
private String crawlerIps = null;
private Pattern ipPattern = null;
private int sessionInactiveInterval = 60;
private boolean isHostAware = true;
private boolean isContextAware = true;
/**
* Specifies a default constructor so async support can be configured.
*/
public CrawlerSessionManagerValve() {
super(true);
}
/**
* Specify the regular expression (using {@link Pattern}) that will be used
* to identify crawlers based in the User-Agent header provided. The default
* is ".*GoogleBot.*|.*bingbot.*|.*Yahoo! Slurp.*"
*
* @param crawlerUserAgents The regular expression using {@link Pattern}
*/
public void setCrawlerUserAgents(String crawlerUserAgents) {
this.crawlerUserAgents = crawlerUserAgents;
if (crawlerUserAgents == null || crawlerUserAgents.length() == 0) {
uaPattern = null;
} else {
uaPattern = Pattern.compile(crawlerUserAgents);
}
}
/**
* @see #setCrawlerUserAgents(String)
* @return The current regular expression being used to match user agents.
*/
public String getCrawlerUserAgents() {
return crawlerUserAgents;
}
/**
* Specify the regular expression (using {@link Pattern}) that will be used
* to identify crawlers based on their IP address. The default is no crawler
* IPs.
*
* @param crawlerIps The regular expression using {@link Pattern}
*/
public void setCrawlerIps(String crawlerIps) {
this.crawlerIps = crawlerIps;
if (crawlerIps == null || crawlerIps.length() == 0) {
ipPattern = null;
} else {
ipPattern = Pattern.compile(crawlerIps);
}
}
/**
* @see #setCrawlerIps(String)
* @return The current regular expression being used to match IP addresses.
*/
public String getCrawlerIps() {
return crawlerIps;
}
/**
* Specify the session timeout (in seconds) for a crawler's session. This is
* typically lower than that for a user session. The default is 60 seconds.
*
* @param sessionInactiveInterval The new timeout for crawler sessions
*/
public void setSessionInactiveInterval(int sessionInactiveInterval) {
this.sessionInactiveInterval = sessionInactiveInterval;
}
/**
* @see #setSessionInactiveInterval(int)
* @return The current timeout in seconds
*/
public int getSessionInactiveInterval() {
return sessionInactiveInterval;
}
public Map<String, String> getClientIpSessionId() {
return clientIdSessionId;
}
public boolean isHostAware() {
return isHostAware;
}
public void setHostAware(boolean isHostAware) {
this.isHostAware = isHostAware;
}
public boolean isContextAware() {
return isContextAware;
}
public void setContextAware(boolean isContextAware) {
this.isContextAware = isContextAware;
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
uaPattern = Pattern.compile(crawlerUserAgents);
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
boolean isBot = false;
String sessionId = null;
String clientIp = request.getRemoteAddr();
String clientIdentifier = getClientIdentifier(request.getHost(), request.getContext(), clientIp);
if (log.isDebugEnabled()) {
log.debug(request.hashCode() + ": ClientIdentifier=" + clientIdentifier + ", RequestedSessionId="
+ request.getRequestedSessionId());
}
// If the incoming request has a valid session ID, no action is required
if (request.getSession(false) == null) {
// Is this a crawler - check the UA headers
Enumeration<String> uaHeaders = request.getHeaders("user-agent");
String uaHeader = null;
if (uaHeaders.hasMoreElements()) {
uaHeader = uaHeaders.nextElement();
}
// If more than one UA header - assume not a bot
if (uaHeader != null && !uaHeaders.hasMoreElements()) {
if (log.isDebugEnabled()) {
log.debug(request.hashCode() + ": UserAgent=" + uaHeader);
}
if (uaPattern.matcher(uaHeader).matches()) {
isBot = true;
if (log.isDebugEnabled()) {
log.debug(request.hashCode() + ": Bot found. UserAgent=" + uaHeader);
}
}
}
if (ipPattern != null && ipPattern.matcher(clientIp).matches()) {
isBot = true;
if (log.isDebugEnabled()) {
log.debug(request.hashCode() + ": Bot found. IP=" + clientIp);
}
}
// If this is a bot, is the session ID known?
if (isBot) {
sessionId = clientIdSessionId.get(clientIdentifier);
if (sessionId != null) {
request.setRequestedSessionId(sessionId);
if (log.isDebugEnabled()) {
log.debug(request.hashCode() + ": SessionID=" + sessionId);
}
}
}
}
getNext().invoke(request, response);
if (isBot) {
if (sessionId == null) {
// Has bot just created a session, if so make a note of it
HttpSession s = request.getSession(false);
if (s != null) {
clientIdSessionId.put(clientIdentifier, s.getId());
sessionIdClientId.put(s.getId(), clientIdentifier);
// #valueUnbound() will be called on session expiration
s.setAttribute(this.getClass().getName(),
new CrawlerHttpSessionBindingListener(clientIdSessionId, clientIdentifier));
s.setMaxInactiveInterval(sessionInactiveInterval);
if (log.isDebugEnabled()) {
log.debug(request.hashCode() + ": New bot session. SessionID=" + s.getId());
}
}
} else {
if (log.isDebugEnabled()) {
log.debug(
request.hashCode() + ": Bot session accessed. SessionID=" + sessionId);
}
}
}
}
private String getClientIdentifier(Host host, Context context, String clientIp) {
StringBuilder result = new StringBuilder(clientIp);
if (isHostAware) {
result.append('-').append(host.getName());
}
if (isContextAware && context != null) {
result.append(context.getName());
}
return result.toString();
}
private static class CrawlerHttpSessionBindingListener implements HttpSessionBindingListener, Serializable {
private static final long serialVersionUID = 1L;
private final transient Map<String, String> clientIdSessionId;
private final transient String clientIdentifier;
private CrawlerHttpSessionBindingListener(Map<String, String> clientIdSessionId, String clientIdentifier) {
this.clientIdSessionId = clientIdSessionId;
this.clientIdentifier = clientIdentifier;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
// NO-OP
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
if (clientIdentifier != null && clientIdSessionId != null) {
clientIdSessionId.remove(clientIdentifier);
}
}
}
}

View File

@@ -0,0 +1,351 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.io.Writer;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.ServerInfo;
import org.apache.catalina.util.TomcatCSS;
import org.apache.coyote.ActionCode;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.security.Escape;
/**
* <p>Implementation of a Valve that outputs HTML error pages.</p>
*
* <p>This Valve should be attached at the Host level, although it will work
* if attached to a Context.</p>
*
* <p>HTML code from the Cocoon 2 project.</p>
*
* @author Remy Maucherat
* @author Craig R. McClanahan
* @author <a href="mailto:nicolaken@supereva.it">Nicola Ken Barozzi</a> Aisa
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @author Yoav Shapira
*/
public class ErrorReportValve extends ValveBase {
private boolean showReport = true;
private boolean showServerInfo = true;
//------------------------------------------------------ Constructor
public ErrorReportValve() {
super(true);
}
// --------------------------------------------------------- Public Methods
/**
* Invoke the next Valve in the sequence. When the invoke returns, check
* the response state. If the status code is greater than or equal to 400
* or an uncaught exception was thrown then the error handling will be
* triggered.
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
// Perform the request
getNext().invoke(request, response);
if (response.isCommitted()) {
if (response.setErrorReported()) {
// Error wasn't previously reported but we can't write an error
// page because the response has already been committed. Attempt
// to flush any data that is still to be written to the client.
try {
response.flushBuffer();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
// Close immediately to signal to the client that something went
// wrong
response.getCoyoteResponse().action(ActionCode.CLOSE_NOW,
request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
}
return;
}
Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// If an async request is in progress and is not going to end once this
// container thread finishes, do not process any error page here.
if (request.isAsync() && !request.isAsyncCompleting()) {
return;
}
if (throwable != null && !response.isError()) {
// Make sure that the necessary methods have been called on the
// response. (It is possible a component may just have set the
// Throwable. Tomcat won't do that but other components might.)
// These are safe to call at this point as we know that the response
// has not been committed.
response.reset();
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
// One way or another, response.sendError() will have been called before
// execution reaches this point and suspended the response. Need to
// reverse that so this valve can write to the response.
response.setSuspended(false);
try {
report(request, response, throwable);
} catch (Throwable tt) {
ExceptionUtils.handleThrowable(tt);
}
}
// ------------------------------------------------------ Protected Methods
/**
* Prints out an error report.
*
* @param request The request being processed
* @param response The response being generated
* @param throwable The exception that occurred (which possibly wraps
* a root cause exception
*/
protected void report(Request request, Response response, Throwable throwable) {
int statusCode = response.getStatus();
// Do nothing on a 1xx, 2xx and 3xx status
// Do nothing if anything has been written already
// Do nothing if the response hasn't been explicitly marked as in error
// and that error has not been reported.
if (statusCode < 400 || response.getContentWritten() > 0 || !response.setErrorReported()) {
return;
}
// If an error has occurred that prevents further I/O, don't waste time
// producing an error report that will never be read
AtomicBoolean result = new AtomicBoolean(false);
response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);
if (!result.get()) {
return;
}
String message = Escape.htmlElementContent(response.getMessage());
if (message == null) {
if (throwable != null) {
String exceptionMessage = throwable.getMessage();
if (exceptionMessage != null && exceptionMessage.length() > 0) {
message = Escape.htmlElementContent((new Scanner(exceptionMessage)).nextLine());
}
}
if (message == null) {
message = "";
}
}
// Do nothing if there is no reason phrase for the specified status code and
// no error message provided
String reason = null;
String description = null;
StringManager smClient = StringManager.getManager(
Constants.Package, request.getLocales());
response.setLocale(smClient.getLocale());
try {
reason = smClient.getString("http." + statusCode + ".reason");
description = smClient.getString("http." + statusCode + ".desc");
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
if (reason == null || description == null) {
if (message.isEmpty()) {
return;
} else {
reason = smClient.getString("errorReportValve.unknownReason");
description = smClient.getString("errorReportValve.noDescription");
}
}
StringBuilder sb = new StringBuilder();
sb.append("<!doctype html><html lang=\"");
sb.append(smClient.getLocale().getLanguage()).append("\">");
sb.append("<head>");
sb.append("<title>");
sb.append(smClient.getString("errorReportValve.statusHeader",
String.valueOf(statusCode), reason));
sb.append("</title>");
sb.append("<style type=\"text/css\">");
sb.append(TomcatCSS.TOMCAT_CSS);
sb.append("</style>");
sb.append("</head><body>");
sb.append("<h1>");
sb.append(smClient.getString("errorReportValve.statusHeader",
String.valueOf(statusCode), reason)).append("</h1>");
if (isShowReport()) {
sb.append("<hr class=\"line\" />");
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.type"));
sb.append("</b> ");
if (throwable != null) {
sb.append(smClient.getString("errorReportValve.exceptionReport"));
} else {
sb.append(smClient.getString("errorReportValve.statusReport"));
}
sb.append("</p>");
if (!message.isEmpty()) {
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.message"));
sb.append("</b> ");
sb.append(message).append("</p>");
}
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.description"));
sb.append("</b> ");
sb.append(description);
sb.append("</p>");
if (throwable != null) {
String stackTrace = getPartialServletStackTrace(throwable);
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.exception"));
sb.append("</b></p><pre>");
sb.append(Escape.htmlElementContent(stackTrace));
sb.append("</pre>");
int loops = 0;
Throwable rootCause = throwable.getCause();
while (rootCause != null && (loops < 10)) {
stackTrace = getPartialServletStackTrace(rootCause);
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.rootCause"));
sb.append("</b></p><pre>");
sb.append(Escape.htmlElementContent(stackTrace));
sb.append("</pre>");
// In case root cause is somehow heavily nested
rootCause = rootCause.getCause();
loops++;
}
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.note"));
sb.append("</b> ");
sb.append(smClient.getString("errorReportValve.rootCauseInLogs"));
sb.append("</p>");
}
sb.append("<hr class=\"line\" />");
}
if (isShowServerInfo()) {
sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
}
sb.append("</body></html>");
try {
try {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("status.setContentType", t);
}
}
Writer writer = response.getReporter();
if (writer != null) {
// If writer is null, it's an indication that the response has
// been hard committed already, which should never happen
writer.write(sb.toString());
response.finishResponse();
}
} catch (IOException e) {
// Ignore
} catch (IllegalStateException e) {
// Ignore
}
}
/**
* Print out a partial servlet stack trace (truncating at the last
* occurrence of javax.servlet.).
* @param t The stack trace to process
* @return the stack trace relative to the application layer
*/
protected String getPartialServletStackTrace(Throwable t) {
StringBuilder trace = new StringBuilder();
trace.append(t.toString()).append(System.lineSeparator());
StackTraceElement[] elements = t.getStackTrace();
int pos = elements.length;
for (int i = elements.length - 1; i >= 0; i--) {
if ((elements[i].getClassName().startsWith
("org.apache.catalina.core.ApplicationFilterChain"))
&& (elements[i].getMethodName().equals("internalDoFilter"))) {
pos = i;
break;
}
}
for (int i = 0; i < pos; i++) {
if (!(elements[i].getClassName().startsWith
("org.apache.catalina.core."))) {
trace.append('\t').append(elements[i].toString()).append(System.lineSeparator());
}
}
return trace.toString();
}
/**
* Enables/Disables full error reports
*
* @param showReport <code>true</code> to show full error data
*/
public void setShowReport(boolean showReport) {
this.showReport = showReport;
}
public boolean isShowReport() {
return showReport;
}
/**
* Enables/Disables server info on error pages
*
* @param showServerInfo <code>true</code> to show server info
*/
public void setShowServerInfo(boolean showServerInfo) {
this.showServerInfo = showServerInfo;
}
public boolean isShowServerInfo() {
return showServerInfo;
}
}

View File

@@ -0,0 +1,871 @@
/*
* 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.catalina.valves;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.ServerInfo;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
/**
* An implementation of the W3c Extended Log File Format. See
* http://www.w3.org/TR/WD-logfile.html for more information about the format.
*
* The following fields are supported:
* <ul>
* <li><code>c-dns</code>: Client hostname (or ip address if
* <code>enableLookups</code> for the connector is false)</li>
* <li><code>c-ip</code>: Client ip address</li>
* <li><code>bytes</code>: bytes served</li>
* <li><code>cs-method</code>: request method</li>
* <li><code>cs-uri</code>: The full uri requested</li>
* <li><code>cs-uri-query</code>: The query string</li>
* <li><code>cs-uri-stem</code>: The uri without query string</li>
* <li><code>date</code>: The date in yyyy-mm-dd format for GMT</li>
* <li><code>s-dns</code>: The server dns entry </li>
* <li><code>s-ip</code>: The server ip address</li>
* <li><code>cs(XXX)</code>: The value of header XXX from client to server</li>
* <li><code>sc(XXX)</code>: The value of header XXX from server to client </li>
* <li><code>sc-status</code>: The status code</li>
* <li><code>time</code>: Time the request was served</li>
* <li><code>time-taken</code>: Time (in seconds) taken to serve the request</li>
* <li><code>x-threadname</code>: Current request thread name (can compare later with stacktraces)</li>
* <li><code>x-A(XXX)</code>: Pull XXX attribute from the servlet context </li>
* <li><code>x-C(XXX)</code>: Pull the first cookie of the name XXX </li>
* <li><code>x-O(XXX)</code>: Pull the all response header values XXX </li>
* <li><code>x-R(XXX)</code>: Pull XXX attribute from the servlet request </li>
* <li><code>x-S(XXX)</code>: Pull XXX attribute from the session </li>
* <li><code>x-P(...)</code>: Call request.getParameter(...)
* and URLencode it. Helpful to capture
* certain POST parameters.
* </li>
* <li>For any of the x-H(...) the following method will be called from the
* HttpServletRequest object </li>
* <li><code>x-H(authType)</code>: getAuthType </li>
* <li><code>x-H(characterEncoding)</code>: getCharacterEncoding </li>
* <li><code>x-H(contentLength)</code>: getContentLength </li>
* <li><code>x-H(locale)</code>: getLocale</li>
* <li><code>x-H(protocol)</code>: getProtocol </li>
* <li><code>x-H(remoteUser)</code>: getRemoteUser</li>
* <li><code>x-H(requestedSessionId)</code>: getRequestedSessionId</li>
* <li><code>x-H(requestedSessionIdFromCookie)</code>:
* isRequestedSessionIdFromCookie </li>
* <li><code>x-H(requestedSessionIdValid)</code>:
* isRequestedSessionIdValid</li>
* <li><code>x-H(scheme)</code>: getScheme</li>
* <li><code>x-H(secure)</code>: isSecure</li>
* </ul>
*
*
*
* <p>
* Log rotation can be on or off. This is dictated by the
* <code>rotatable</code> property.
* </p>
*
* <p>
* For UNIX users, another field called <code>checkExists</code> is also
* available. If set to true, the log file's existence will be checked before
* each logging. This way an external log rotator can move the file
* somewhere and Tomcat will start with a new file.
* </p>
*
* <p>
* For JMX junkies, a public method called <code>rotate</code> has
* been made available to allow you to tell this instance to move
* the existing log file to somewhere else and start writing a new log file.
* </p>
*
* <p>
* Conditional logging is also supported. This can be done with the
* <code>condition</code> property.
* If the value returned from ServletRequest.getAttribute(condition)
* yields a non-null value, the logging will be skipped.
* </p>
*
* <p>
* For extended attributes coming from a getAttribute() call,
* it is you responsibility to ensure there are no newline or
* control characters.
* </p>
*
* @author Peter Rossbach
*/
public class ExtendedAccessLogValve extends AccessLogValve {
private static final Log log = LogFactory.getLog(ExtendedAccessLogValve.class);
// ----------------------------------------------------- Instance Variables
/**
* The descriptive information about this implementation.
*/
protected static final String extendedAccessLogInfo =
"org.apache.catalina.valves.ExtendedAccessLogValve/2.1";
// -------------------------------------------------------- Private Methods
/**
* Wrap the incoming value with double quotes (") and escape any double
* quotes appearing in the value using two double quotes ("").
*
* @param value - The value to wrap
* @return '-' if null. Otherwise, toString() will
* be called on the object and the value will be wrapped
* in quotes and any quotes will be escaped with 2
* sets of quotes.
*/
static String wrap(Object value) {
String svalue;
// Does the value contain a " ? If so must encode it
if (value == null || "-".equals(value)) {
return "-";
}
try {
svalue = value.toString();
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
/* Log error */
return "-";
}
/* Wrap all values in double quotes. */
StringBuilder buffer = new StringBuilder(svalue.length() + 2);
buffer.append('\"');
int i = 0;
while (i < svalue.length()) {
int j = svalue.indexOf('\"', i);
if (j == -1) {
buffer.append(svalue.substring(i));
i = svalue.length();
} else {
buffer.append(svalue.substring(i, j + 1));
buffer.append('"');
i = j + 1;
}
}
buffer.append('\"');
return buffer.toString();
}
/**
* Open the new log file for the date specified by <code>dateStamp</code>.
*/
@Override
protected synchronized void open() {
super.open();
if (currentLogFile.length()==0) {
writer.println("#Fields: " + pattern);
writer.println("#Version: 2.0");
writer.println("#Software: " + ServerInfo.getServerInfo());
}
}
// ------------------------------------------------------ Lifecycle Methods
protected static class DateElement implements AccessLogElement {
// Milli-seconds in 24 hours
private static final long INTERVAL = (1000 * 60 * 60 * 24);
private static final ThreadLocal<ElementTimestampStruct> currentDate =
new ThreadLocal<ElementTimestampStruct>() {
@Override
protected ElementTimestampStruct initialValue() {
return new ElementTimestampStruct("yyyy-MM-dd");
}
};
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
ElementTimestampStruct eds = currentDate.get();
long millis = eds.currentTimestamp.getTime();
if (date.getTime() > (millis + INTERVAL -1) ||
date.getTime() < millis) {
eds.currentTimestamp.setTime(
date.getTime() - (date.getTime() % INTERVAL));
eds.currentTimestampString =
eds.currentTimestampFormat.format(eds.currentTimestamp);
}
buf.append(eds.currentTimestampString);
}
}
protected static class TimeElement implements AccessLogElement {
// Milli-seconds in a second
private static final long INTERVAL = 1000;
private static final ThreadLocal<ElementTimestampStruct> currentTime =
new ThreadLocal<ElementTimestampStruct>() {
@Override
protected ElementTimestampStruct initialValue() {
return new ElementTimestampStruct("HH:mm:ss");
}
};
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
ElementTimestampStruct eds = currentTime.get();
long millis = eds.currentTimestamp.getTime();
if (date.getTime() > (millis + INTERVAL -1) ||
date.getTime() < millis) {
eds.currentTimestamp.setTime(
date.getTime() - (date.getTime() % INTERVAL));
eds.currentTimestampString =
eds.currentTimestampFormat.format(eds.currentTimestamp);
}
buf.append(eds.currentTimestampString);
}
}
protected static class RequestHeaderElement implements AccessLogElement {
private final String header;
public RequestHeaderElement(String header) {
this.header = header;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
buf.append(wrap(request.getHeader(header)));
}
}
protected static class ResponseHeaderElement implements AccessLogElement {
private final String header;
public ResponseHeaderElement(String header) {
this.header = header;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
buf.append(wrap(response.getHeader(header)));
}
}
protected static class ServletContextElement implements AccessLogElement {
private final String attribute;
public ServletContextElement(String attribute) {
this.attribute = attribute;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
buf.append(wrap(request.getContext().getServletContext()
.getAttribute(attribute)));
}
}
protected static class CookieElement implements AccessLogElement {
private final String name;
public CookieElement(String name) {
this.name = name;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
Cookie[] c = request.getCookies();
for (int i = 0; c != null && i < c.length; i++) {
if (name.equals(c[i].getName())) {
buf.append(wrap(c[i].getValue()));
}
}
}
}
/**
* write a specific response header - x-O(xxx)
*/
protected static class ResponseAllHeaderElement implements AccessLogElement {
private final String header;
public ResponseAllHeaderElement(String header) {
this.header = header;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
if (null != response) {
Iterator<String> iter = response.getHeaders(header).iterator();
if (iter.hasNext()) {
StringBuilder buffer = new StringBuilder();
boolean first = true;
while (iter.hasNext()) {
if (first) {
first = false;
} else {
buffer.append(",");
}
buffer.append(iter.next());
}
buf.append(wrap(buffer.toString()));
}
return ;
}
buf.append("-");
}
}
protected static class RequestAttributeElement implements AccessLogElement {
private final String attribute;
public RequestAttributeElement(String attribute) {
this.attribute = attribute;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
buf.append(wrap(request.getAttribute(attribute)));
}
}
protected static class SessionAttributeElement implements AccessLogElement {
private final String attribute;
public SessionAttributeElement(String attribute) {
this.attribute = attribute;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
HttpSession session = null;
if (request != null) {
session = request.getSession(false);
if (session != null) {
buf.append(wrap(session.getAttribute(attribute)));
}
}
}
}
protected static class RequestParameterElement implements AccessLogElement {
private final String parameter;
public RequestParameterElement(String parameter) {
this.parameter = parameter;
}
/**
* urlEncode the given string. If null or empty, return null.
*/
private String urlEncode(String value) {
if (null==value || value.length()==0) {
return null;
}
try {
return URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Should never happen - all JVMs are required to support UTF-8
return null;
}
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
buf.append(wrap(urlEncode(request.getParameter(parameter))));
}
}
protected static class PatternTokenizer {
private final StringReader sr;
private StringBuilder buf = new StringBuilder();
private boolean ended = false;
private boolean subToken;
private boolean parameter;
public PatternTokenizer(String str) {
sr = new StringReader(str);
}
public boolean hasSubToken() {
return subToken;
}
public boolean hasParameter() {
return parameter;
}
public String getToken() throws IOException {
if(ended) {
return null ;
}
String result = null;
subToken = false;
parameter = false;
int c = sr.read();
while (c != -1) {
switch (c) {
case ' ':
result = buf.toString();
buf = new StringBuilder();
buf.append((char) c);
return result;
case '-':
result = buf.toString();
buf = new StringBuilder();
subToken = true;
return result;
case '(':
result = buf.toString();
buf = new StringBuilder();
parameter = true;
return result;
case ')':
result = buf.toString();
buf = new StringBuilder();
break;
default:
buf.append((char) c);
}
c = sr.read();
}
ended = true;
if (buf.length() != 0) {
return buf.toString();
} else {
return null;
}
}
public String getParameter()throws IOException {
String result;
if (!parameter) {
return null;
}
parameter = false;
int c = sr.read();
while (c != -1) {
if (c == ')') {
result = buf.toString();
buf = new StringBuilder();
return result;
}
buf.append((char) c);
c = sr.read();
}
return null;
}
public String getWhiteSpaces() throws IOException {
if(isEnded()) {
return "" ;
}
StringBuilder whiteSpaces = new StringBuilder();
if (buf.length() > 0) {
whiteSpaces.append(buf);
buf = new StringBuilder();
}
int c = sr.read();
while (Character.isWhitespace((char) c)) {
whiteSpaces.append((char) c);
c = sr.read();
}
if (c == -1) {
ended = true;
} else {
buf.append((char) c);
}
return whiteSpaces.toString();
}
public boolean isEnded() {
return ended;
}
public String getRemains() throws IOException {
StringBuilder remains = new StringBuilder();
for(int c = sr.read(); c != -1; c = sr.read()) {
remains.append((char) c);
}
return remains.toString();
}
}
@Override
protected AccessLogElement[] createLogElements() {
if (log.isDebugEnabled()) {
log.debug("decodePattern, pattern =" + pattern);
}
List<AccessLogElement> list = new ArrayList<>();
PatternTokenizer tokenizer = new PatternTokenizer(pattern);
try {
// Ignore leading whitespace.
tokenizer.getWhiteSpaces();
if (tokenizer.isEnded()) {
log.info(sm.getString("extendedAccessLogValve.emptyPattern"));
return null;
}
String token = tokenizer.getToken();
while (token != null) {
if (log.isDebugEnabled()) {
log.debug("token = " + token);
}
AccessLogElement element = getLogElement(token, tokenizer);
if (element == null) {
break;
}
list.add(element);
String whiteSpaces = tokenizer.getWhiteSpaces();
if (whiteSpaces.length() > 0) {
list.add(new StringElement(whiteSpaces));
}
if (tokenizer.isEnded()) {
break;
}
token = tokenizer.getToken();
}
if (log.isDebugEnabled()) {
log.debug("finished decoding with element size of: " + list.size());
}
return list.toArray(new AccessLogElement[0]);
} catch (IOException e) {
log.error(sm.getString("extendedAccessLogValve.patternParseError", pattern), e);
return null;
}
}
protected AccessLogElement getLogElement(String token, PatternTokenizer tokenizer) throws IOException {
if ("date".equals(token)) {
return new DateElement();
} else if ("time".equals(token)) {
if (tokenizer.hasSubToken()) {
String nextToken = tokenizer.getToken();
if ("taken".equals(nextToken)) {
return new ElapsedTimeElement(false);
}
} else {
return new TimeElement();
}
} else if ("bytes".equals(token)) {
return new ByteSentElement(true);
} else if ("cached".equals(token)) {
/* I don't know how to evaluate this! */
return new StringElement("-");
} else if ("c".equals(token)) {
String nextToken = tokenizer.getToken();
if ("ip".equals(nextToken)) {
return new RemoteAddrElement();
} else if ("dns".equals(nextToken)) {
return new HostElement();
}
} else if ("s".equals(token)) {
String nextToken = tokenizer.getToken();
if ("ip".equals(nextToken)) {
return new LocalAddrElement(getIpv6Canonical());
} else if ("dns".equals(nextToken)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
String value;
try {
value = InetAddress.getLocalHost().getHostName();
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
value = "localhost";
}
buf.append(value);
}
};
}
} else if ("cs".equals(token)) {
return getClientToServerElement(tokenizer);
} else if ("sc".equals(token)) {
return getServerToClientElement(tokenizer);
} else if ("sr".equals(token) || "rs".equals(token)) {
return getProxyElement(tokenizer);
} else if ("x".equals(token)) {
return getXParameterElement(tokenizer);
}
log.error(sm.getString("extendedAccessLogValve.decodeError", token));
return null;
}
protected AccessLogElement getClientToServerElement(
PatternTokenizer tokenizer) throws IOException {
if (tokenizer.hasSubToken()) {
String token = tokenizer.getToken();
if ("method".equals(token)) {
return new MethodElement();
} else if ("uri".equals(token)) {
if (tokenizer.hasSubToken()) {
token = tokenizer.getToken();
if ("stem".equals(token)) {
return new RequestURIElement();
} else if ("query".equals(token)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf,
Date date, Request request,
Response response, long time) {
String query = request.getQueryString();
if (query != null) {
buf.append(query);
} else {
buf.append('-');
}
}
};
}
} else {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
String query = request.getQueryString();
if (query == null) {
buf.append(request.getRequestURI());
} else {
buf.append(request.getRequestURI());
buf.append('?');
buf.append(request.getQueryString());
}
}
};
}
}
} else if (tokenizer.hasParameter()) {
String parameter = tokenizer.getParameter();
if (parameter == null) {
log.error(sm.getString("extendedAccessLogValve.noClosing"));
return null;
}
return new RequestHeaderElement(parameter);
}
log.error(sm.getString("extendedAccessLogValve.decodeError", tokenizer.getRemains()));
return null;
}
protected AccessLogElement getServerToClientElement(
PatternTokenizer tokenizer) throws IOException {
if (tokenizer.hasSubToken()) {
String token = tokenizer.getToken();
if ("status".equals(token)) {
return new HttpStatusCodeElement();
} else if ("comment".equals(token)) {
return new StringElement("?");
}
} else if (tokenizer.hasParameter()) {
String parameter = tokenizer.getParameter();
if (parameter == null) {
log.error(sm.getString("extendedAccessLogValve.noClosing"));
return null;
}
return new ResponseHeaderElement(parameter);
}
log.error(sm.getString("extendedAccessLogValve.decodeError", tokenizer.getRemains()));
return null;
}
protected AccessLogElement getProxyElement(PatternTokenizer tokenizer)
throws IOException {
String token = null;
if (tokenizer.hasSubToken()) {
tokenizer.getToken();
return new StringElement("-");
} else if (tokenizer.hasParameter()) {
tokenizer.getParameter();
return new StringElement("-");
}
log.error(sm.getString("extendedAccessLogValve.decodeError", token));
return null;
}
protected AccessLogElement getXParameterElement(PatternTokenizer tokenizer)
throws IOException {
if (!tokenizer.hasSubToken()) {
log.error(sm.getString("extendedAccessLogValve.badXParam"));
return null;
}
String token = tokenizer.getToken();
if ("threadname".equals(token)) {
return new ThreadNameElement();
}
if (!tokenizer.hasParameter()) {
log.error(sm.getString("extendedAccessLogValve.badXParam"));
return null;
}
String parameter = tokenizer.getParameter();
if (parameter == null) {
log.error(sm.getString("extendedAccessLogValve.noClosing"));
return null;
}
if ("A".equals(token)) {
return new ServletContextElement(parameter);
} else if ("C".equals(token)) {
return new CookieElement(parameter);
} else if ("R".equals(token)) {
return new RequestAttributeElement(parameter);
} else if ("S".equals(token)) {
return new SessionAttributeElement(parameter);
} else if ("H".equals(token)) {
return getServletRequestElement(parameter);
} else if ("P".equals(token)) {
return new RequestParameterElement(parameter);
} else if ("O".equals(token)) {
return new ResponseAllHeaderElement(parameter);
}
log.error(sm.getString("extendedAccessLogValve.badXParamValue", token));
return null;
}
protected AccessLogElement getServletRequestElement(String parameter) {
if ("authType".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap(request.getAuthType()));
}
};
} else if ("remoteUser".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap(request.getRemoteUser()));
}
};
} else if ("requestedSessionId".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap(request.getRequestedSessionId()));
}
};
} else if ("requestedSessionIdFromCookie".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap(""
+ request.isRequestedSessionIdFromCookie()));
}
};
} else if ("requestedSessionIdValid".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap("" + request.isRequestedSessionIdValid()));
}
};
} else if ("contentLength".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap("" + request.getContentLengthLong()));
}
};
} else if ("characterEncoding".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap(request.getCharacterEncoding()));
}
};
} else if ("locale".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap(request.getLocale()));
}
};
} else if ("protocol".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap(request.getProtocol()));
}
};
} else if ("scheme".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(request.getScheme());
}
};
} else if ("secure".equals(parameter)) {
return new AccessLogElement() {
@Override
public void addElement(CharArrayWriter buf, Date date,
Request request, Response response, long time) {
buf.append(wrap("" + request.isSecure()));
}
};
}
log.error(sm.getString("extendedAccessLogValve.badXParamValue", parameter));
return null;
}
private static class ElementTimestampStruct {
private final Date currentTimestamp = new Date(0);
private final SimpleDateFormat currentTimestampFormat;
private String currentTimestampString;
ElementTimestampStruct(String format) {
currentTimestampFormat = new SimpleDateFormat(format, Locale.US);
currentTimestampFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
}
}
}

View File

@@ -0,0 +1,684 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Properties;
import javax.servlet.ServletException;
import org.apache.catalina.AccessLog;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.tomcat.util.ExceptionUtils;
/**
* <p>
* This Tomcat extension logs server access directly to a database, and can
* be used instead of the regular file-based access log implemented in
* AccessLogValve.
* To use, copy into the server/classes directory of the Tomcat installation
* and configure in server.xml as:
* </p>
* <pre>
* &lt;Valve className="org.apache.catalina.valves.JDBCAccessLogValve"
* driverName="<i>your_jdbc_driver</i>"
* connectionURL="<i>your_jdbc_url</i>"
* pattern="combined" resolveHosts="false"
* /&gt;
* </pre>
* <p>
* Many parameters can be configured, such as the database connection (with
* <code>driverName</code> and <code>connectionURL</code>),
* the table name (<code>tableName</code>)
* and the field names (corresponding to the get/set method names).
* The same options as AccessLogValve are supported, such as
* <code>resolveHosts</code> and <code>pattern</code> ("common" or "combined"
* only).
* </p>
* <p>
* When Tomcat is started, a database connection is created and used for all the
* log activity. When Tomcat is shutdown, the database connection is closed.
* This logger can be used at the level of the Engine context (being shared
* by all the defined hosts) or the Host context (one instance of the logger
* per host, possibly using different databases).
* </p>
* <p>
* The database table can be created with the following command:
* </p>
* <pre>
* CREATE TABLE access (
* id INT UNSIGNED AUTO_INCREMENT NOT NULL,
* remoteHost CHAR(15) NOT NULL,
* userName CHAR(15),
* timestamp TIMESTAMP NOT NULL,
* virtualHost VARCHAR(64) NOT NULL,
* method VARCHAR(8) NOT NULL,
* query VARCHAR(255) NOT NULL,
* status SMALLINT UNSIGNED NOT NULL,
* bytes INT UNSIGNED NOT NULL,
* referer VARCHAR(128),
* userAgent VARCHAR(128),
* PRIMARY KEY (id),
* INDEX (timestamp),
* INDEX (remoteHost),
* INDEX (virtualHost),
* INDEX (query),
* INDEX (userAgent)
* );
* </pre>
* <p>Set JDBCAccessLogValve attribute useLongContentLength="true" as you have more then 4GB outputs.
* Please, use long SQL datatype at access.bytes attribute.
* The datatype of bytes at oracle is <i>number</i> and other databases use <i>bytes BIGINT NOT NULL</i>.
* </p>
*
* <p>
* If the table is created as above, its name and the field names don't need
* to be defined.
* </p>
* <p>
* If the request method is "common", only these fields are used:
* <code>remoteHost, user, timeStamp, query, status, bytes</code>
* </p>
* <p>
* <i>TO DO: provide option for excluding logging of certain MIME types.</i>
* </p>
*
* @author Andre de Jesus
* @author Peter Rossbach
*/
public final class JDBCAccessLogValve extends ValveBase implements AccessLog {
// ----------------------------------------------------------- Constructors
/**
* Class constructor. Initializes the fields with the default values.
* The defaults are:
* <pre>
* driverName = null;
* connectionURL = null;
* tableName = "access";
* remoteHostField = "remoteHost";
* userField = "userName";
* timestampField = "timestamp";
* virtualHostField = "virtualHost";
* methodField = "method";
* queryField = "query";
* statusField = "status";
* bytesField = "bytes";
* refererField = "referer";
* userAgentField = "userAgent";
* pattern = "common";
* resolveHosts = false;
* </pre>
*/
public JDBCAccessLogValve() {
super(true);
driverName = null;
connectionURL = null;
tableName = "access";
remoteHostField = "remoteHost";
userField = "userName";
timestampField = "timestamp";
virtualHostField = "virtualHost";
methodField = "method";
queryField = "query";
statusField = "status";
bytesField = "bytes";
refererField = "referer";
userAgentField = "userAgent";
pattern = "common";
resolveHosts = false;
conn = null;
ps = null;
currentTimeMillis = new java.util.Date().getTime();
}
// ----------------------------------------------------- Instance Variables
/**
* Use long contentLength as you have more 4 GB output.
* @since 6.0.15
*/
boolean useLongContentLength = false;
/**
* The connection username to use when trying to connect to the database.
*/
String connectionName = null;
/**
* The connection URL to use when trying to connect to the database.
*/
String connectionPassword = null;
/**
* Instance of the JDBC Driver class we use as a connection factory.
*/
Driver driver = null;
private String driverName;
private String connectionURL;
private String tableName;
private String remoteHostField;
private String userField;
private String timestampField;
private String virtualHostField;
private String methodField;
private String queryField;
private String statusField;
private String bytesField;
private String refererField;
private String userAgentField;
private String pattern;
private boolean resolveHosts;
private Connection conn;
private PreparedStatement ps;
private long currentTimeMillis;
/**
* Should this valve set request attributes for IP address, hostname,
* protocol and port used for the request.
* Default is <code>true</code>.
* @see #setRequestAttributesEnabled(boolean)
*/
boolean requestAttributesEnabled = true;
// ------------------------------------------------------------- Properties
/**
* {@inheritDoc}
* Default is <code>true</code>.
*/
@Override
public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
this.requestAttributesEnabled = requestAttributesEnabled;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getRequestAttributesEnabled() {
return requestAttributesEnabled;
}
/**
* @return the username to use to connect to the database.
*/
public String getConnectionName() {
return connectionName;
}
/**
* Set the username to use to connect to the database.
*
* @param connectionName Username
*/
public void setConnectionName(String connectionName) {
this.connectionName = connectionName;
}
/**
* Sets the database driver name.
*
* @param driverName The complete name of the database driver class.
*/
public void setDriverName(String driverName) {
this.driverName = driverName;
}
/**
* @return the password to use to connect to the database.
*/
public String getConnectionPassword() {
return connectionPassword;
}
/**
* Set the password to use to connect to the database.
*
* @param connectionPassword User password
*/
public void setConnectionPassword(String connectionPassword) {
this.connectionPassword = connectionPassword;
}
/**
* Sets the JDBC URL for the database where the log is stored.
*
* @param connectionURL The JDBC URL of the database.
*/
public void setConnectionURL(String connectionURL) {
this.connectionURL = connectionURL;
}
/**
* Sets the name of the table where the logs are stored.
*
* @param tableName The name of the table.
*/
public void setTableName(String tableName) {
this.tableName = tableName;
}
/**
* Sets the name of the field containing the remote host.
*
* @param remoteHostField The name of the remote host field.
*/
public void setRemoteHostField(String remoteHostField) {
this.remoteHostField = remoteHostField;
}
/**
* Sets the name of the field containing the remote user name.
*
* @param userField The name of the remote user field.
*/
public void setUserField(String userField) {
this.userField = userField;
}
/**
* Sets the name of the field containing the server-determined timestamp.
*
* @param timestampField The name of the server-determined timestamp field.
*/
public void setTimestampField(String timestampField) {
this.timestampField = timestampField;
}
/**
* Sets the name of the field containing the virtual host information
* (this is in fact the server name).
*
* @param virtualHostField The name of the virtual host field.
*/
public void setVirtualHostField(String virtualHostField) {
this.virtualHostField = virtualHostField;
}
/**
* Sets the name of the field containing the HTTP request method.
*
* @param methodField The name of the HTTP request method field.
*/
public void setMethodField(String methodField) {
this.methodField = methodField;
}
/**
* Sets the name of the field containing the URL part of the HTTP query.
*
* @param queryField The name of the field containing the URL part of
* the HTTP query.
*/
public void setQueryField(String queryField) {
this.queryField = queryField;
}
/**
* Sets the name of the field containing the HTTP response status code.
*
* @param statusField The name of the HTTP response status code field.
*/
public void setStatusField(String statusField) {
this.statusField = statusField;
}
/**
* Sets the name of the field containing the number of bytes returned.
*
* @param bytesField The name of the returned bytes field.
*/
public void setBytesField(String bytesField) {
this.bytesField = bytesField;
}
/**
* Sets the name of the field containing the referer.
*
* @param refererField The referer field name.
*/
public void setRefererField(String refererField) {
this.refererField = refererField;
}
/**
* Sets the name of the field containing the user agent.
*
* @param userAgentField The name of the user agent field.
*/
public void setUserAgentField(String userAgentField) {
this.userAgentField = userAgentField;
}
/**
* Sets the logging pattern. The patterns supported correspond to the
* file-based "common" and "combined". These are translated into the use
* of tables containing either set of fields.
* <P><I>TO DO: more flexible field choices.</I></P>
*
* @param pattern The name of the logging pattern.
*/
public void setPattern(String pattern) {
this.pattern = pattern;
}
/**
* Determines whether IP host name resolution is done.
*
* @param resolveHosts "true" or "false", if host IP resolution
* is desired or not.
*/
public void setResolveHosts(String resolveHosts) {
this.resolveHosts = Boolean.parseBoolean(resolveHosts);
}
/**
* @return <code>true</code> if content length should be considered a long
* rather than an int, defaults to <code>false</code>
*/
public boolean getUseLongContentLength() {
return this.useLongContentLength;
}
/**
* @param useLongContentLength the useLongContentLength to set
*/
public void setUseLongContentLength(boolean useLongContentLength) {
this.useLongContentLength = useLongContentLength;
}
// --------------------------------------------------------- Public Methods
/**
* This method is invoked by Tomcat on each query.
*
* @param request The Request object.
* @param response The Response object.
*
* @exception IOException Should not be thrown.
* @exception ServletException Database SQLException is wrapped
* in a ServletException.
*/
@Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
getNext().invoke(request, response);
}
@Override
public void log(Request request, Response response, long time) {
if (!getState().isAvailable()) {
return;
}
final String EMPTY = "" ;
String remoteHost;
if(resolveHosts) {
if (requestAttributesEnabled) {
Object host = request.getAttribute(REMOTE_HOST_ATTRIBUTE);
if (host == null) {
remoteHost = request.getRemoteHost();
} else {
remoteHost = (String) host;
}
} else {
remoteHost = request.getRemoteHost();
}
} else {
if (requestAttributesEnabled) {
Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
if (addr == null) {
remoteHost = request.getRemoteAddr();
} else {
remoteHost = (String) addr;
}
} else {
remoteHost = request.getRemoteAddr();
}
}
String user = request.getRemoteUser();
String query=request.getRequestURI();
long bytes = response.getBytesWritten(true);
if(bytes < 0) {
bytes = 0;
}
int status = response.getStatus();
String virtualHost = EMPTY;
String method = EMPTY;
String referer = EMPTY;
String userAgent = EMPTY;
String logPattern = pattern;
if (logPattern.equals("combined")) {
virtualHost = request.getServerName();
method = request.getMethod();
referer = request.getHeader("referer");
userAgent = request.getHeader("user-agent");
}
synchronized (this) {
int numberOfTries = 2;
while (numberOfTries>0) {
try {
open();
ps.setString(1, remoteHost);
ps.setString(2, user);
ps.setTimestamp(3, new Timestamp(getCurrentTimeMillis()));
ps.setString(4, query);
ps.setInt(5, status);
if(useLongContentLength) {
ps.setLong(6, bytes);
} else {
if (bytes > Integer.MAX_VALUE) {
bytes = -1 ;
}
ps.setInt(6, (int) bytes);
}
if (logPattern.equals("combined")) {
ps.setString(7, virtualHost);
ps.setString(8, method);
ps.setString(9, referer);
ps.setString(10, userAgent);
}
ps.executeUpdate();
return;
} catch (SQLException e) {
// Log the problem for posterity
container.getLogger().error(sm.getString("jdbcAccessLogValve.exception"), e);
// Close the connection so that it gets reopened next time
if (conn != null) {
close();
}
}
numberOfTries--;
}
}
}
/**
* Open (if necessary) and return a database connection for use by
* this AccessLogValve.
*
* @exception SQLException if a database error occurs
*/
protected void open() throws SQLException {
// Do nothing if there is a database connection already open
if (conn != null) {
return ;
}
// Instantiate our database driver if necessary
if (driver == null) {
try {
Class<?> clazz = Class.forName(driverName);
driver = (Driver) clazz.getConstructor().newInstance();
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new SQLException(e.getMessage(), e);
}
}
// Open a new connection
Properties props = new Properties();
if (connectionName != null) {
props.put("user", connectionName);
}
if (connectionPassword != null) {
props.put("password", connectionPassword);
}
conn = driver.connect(connectionURL, props);
conn.setAutoCommit(true);
String logPattern = pattern;
if (logPattern.equals("common")) {
ps = conn.prepareStatement
("INSERT INTO " + tableName + " ("
+ remoteHostField + ", " + userField + ", "
+ timestampField +", " + queryField + ", "
+ statusField + ", " + bytesField
+ ") VALUES(?, ?, ?, ?, ?, ?)");
} else if (logPattern.equals("combined")) {
ps = conn.prepareStatement
("INSERT INTO " + tableName + " ("
+ remoteHostField + ", " + userField + ", "
+ timestampField + ", " + queryField + ", "
+ statusField + ", " + bytesField + ", "
+ virtualHostField + ", " + methodField + ", "
+ refererField + ", " + userAgentField
+ ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
}
}
/**
* Close the specified database connection.
*/
protected void close() {
// Do nothing if the database connection is already closed
if (conn == null) {
return;
}
// Close our prepared statements (if any)
try {
ps.close();
} catch (Throwable f) {
ExceptionUtils.handleThrowable(f);
}
this.ps = null;
// Close this database connection, and log any errors
try {
conn.close();
} catch (SQLException e) {
container.getLogger().error(sm.getString("jdbcAccessLogValve.close"), e); // Just log it here
} finally {
this.conn = null;
}
}
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
try {
open() ;
} catch (SQLException e) {
throw new LifecycleException(e);
}
setState(LifecycleState.STARTING);
}
/**
* Stop this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
close() ;
}
public long getCurrentTimeMillis() {
long systime = System.currentTimeMillis();
if ((systime - currentTimeMillis) > 1000) {
currentTimeMillis = new java.util.Date(systime).getTime();
}
return currentTimeMillis;
}
}

View File

@@ -0,0 +1,249 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.SessionConfig;
/**
* <p>A Valve to detect situations where a load-balanced node receiving a
* request has been deactivated by the load balancer (JK_LB_ACTIVATION=DIS)
* and the incoming request has no valid session.</p>
*
* <p>In these cases, the user's session cookie should be removed if it exists,
* any ";jsessionid" parameter should be removed from the request URI,
* and the client should be redirected to the same URI. This will cause the
* load-balanced to re-balance the client to another server.</p>
*
* <p>All this work is required because when the activation state of a node is
* DISABLED, the load-balancer will still send requests to the node if they
* appear to have a session on that node. Since mod_jk doesn't actually know
* whether the session id is valid, it will send the request blindly to
* the disabled node, which makes it take much longer to drain the node
* than strictly necessary.</p>
*
* <p>For testing purposes, a special cookie can be configured and used
* by a client to ignore the normal behavior of this Valve and allow
* a client to get a new session on a DISABLED node. See
* {@link #setIgnoreCookieName} and {@link #setIgnoreCookieValue}
* to configure those values.</p>
*
* <p>This Valve should be installed earlier in the Valve pipeline than any
* authentication valves, as the redirection should take place before an
* authentication valve would save a request to a protected resource.</p>
*
* @see <a href="https://tomcat.apache.org/connectors-doc/generic_howto/loadbalancers.html">Load
* balancer documentation</a>
*/
public class LoadBalancerDrainingValve extends ValveBase {
/**
* The request attribute key where the load-balancer's activation state
* can be found.
*/
public static final String ATTRIBUTE_KEY_JK_LB_ACTIVATION = "JK_LB_ACTIVATION";
/**
* The HTTP response code that will be used to redirect the request
* back to the load-balancer for re-balancing. Defaults to 307
* (TEMPORARY_REDIRECT).
*
* HTTP status code 305 (USE_PROXY) might be an option, here. too.
*/
private int _redirectStatusCode = HttpServletResponse.SC_TEMPORARY_REDIRECT;
/**
* The name of the cookie which can be set to ignore the "draining" action
* of this Filter. This will allow a client to contact the server without
* being re-balanced to another server. The expected cookie value can be set
* in the {@link #_ignoreCookieValue}. The cookie name and value must match
* to avoid being re-balanced.
*/
private String _ignoreCookieName;
/**
* The value of the cookie which can be set to ignore the "draining" action
* of this Filter. This will allow a client to contact the server without
* being re-balanced to another server. The expected cookie name can be set
* in the {@link #_ignoreCookieName}. The cookie name and value must match
* to avoid being re-balanced.
*/
private String _ignoreCookieValue;
public LoadBalancerDrainingValve() {
super(true); // Supports async
}
//
// Configuration parameters
//
/**
* Sets the HTTP response code that will be used to redirect the request
* back to the load-balancer for re-balancing. Defaults to 307
* (TEMPORARY_REDIRECT).
*
* @param code The code to use for the redirect
*/
public void setRedirectStatusCode(int code) {
_redirectStatusCode = code;
}
/**
* Gets the name of the cookie that can be used to override the
* re-balancing behavior of this Valve when the current node is
* in the DISABLED activation state.
*
* @return The cookie name used to ignore normal processing rules.
*
* @see #setIgnoreCookieValue
*/
public String getIgnoreCookieName() {
return _ignoreCookieName;
}
/**
* Sets the name of the cookie that can be used to override the
* re-balancing behavior of this Valve when the current node is
* in the DISABLED activation state.
*
* There is no default value for this setting: the ability to override
* the re-balancing behavior of this Valve is <i>disabled</i> by default.
*
* @param cookieName The cookie name to use to ignore normal
* processing rules.
*
* @see #getIgnoreCookieValue
*/
public void setIgnoreCookieName(String cookieName) {
_ignoreCookieName = cookieName;
}
/**
* Gets the expected value of the cookie that can be used to override the
* re-balancing behavior of this Valve when the current node is
* in the DISABLED activation state.
*
* @return The cookie value used to ignore normal processing rules.
*
* @see #setIgnoreCookieValue
*/
public String getIgnoreCookieValue() {
return _ignoreCookieValue;
}
/**
* Sets the expected value of the cookie that can be used to override the
* re-balancing behavior of this Valve when the current node is
* in the DISABLED activation state. The "ignore" cookie's value
* <b>must</b> be exactly equal to this value in order to allow
* the client to override the re-balancing behavior.
*
* @param cookieValue The cookie value to use to ignore normal
* processing rules.
*
* @see #getIgnoreCookieValue
*/
public void setIgnoreCookieValue(String cookieValue) {
_ignoreCookieValue = cookieValue;
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
if ("DIS".equals(request.getAttribute(ATTRIBUTE_KEY_JK_LB_ACTIVATION)) &&
!request.isRequestedSessionIdValid()) {
if (containerLog.isDebugEnabled()) {
containerLog.debug("Load-balancer is in DISABLED state; draining this node");
}
boolean ignoreRebalance = false;
Cookie sessionCookie = null;
final Cookie[] cookies = request.getCookies();
final String sessionCookieName = SessionConfig.getSessionCookieName(request.getContext());
if (null != cookies) {
for (Cookie cookie : cookies) {
final String cookieName = cookie.getName();
if (containerLog.isTraceEnabled()) {
containerLog.trace("Checking cookie " + cookieName + "=" + cookie.getValue());
}
if (sessionCookieName.equals(cookieName) &&
request.getRequestedSessionId().equals(cookie.getValue())) {
sessionCookie = cookie;
} else if (null != _ignoreCookieName &&
_ignoreCookieName.equals(cookieName) &&
null != _ignoreCookieValue &&
_ignoreCookieValue.equals(cookie.getValue())) {
// The client presenting a valid ignore-cookie value?
ignoreRebalance = true;
}
}
}
if (ignoreRebalance) {
if (containerLog.isDebugEnabled()) {
containerLog.debug("Client is presenting a valid " + _ignoreCookieName +
" cookie, re-balancing is being skipped");
}
getNext().invoke(request, response);
return;
}
// Kill any session cookie that was found
// TODO: Consider implications of SSO cookies
if (null != sessionCookie) {
sessionCookie.setPath(SessionConfig.getSessionCookiePath(request.getContext()));
sessionCookie.setMaxAge(0); // Delete
sessionCookie.setValue(""); // Purge the cookie's value
response.addCookie(sessionCookie);
}
// Re-write the URI if it contains a ;jsessionid parameter
String uri = request.getRequestURI();
String sessionURIParamName = SessionConfig.getSessionUriParamName(request.getContext());
if (uri.contains(";" + sessionURIParamName + "=")) {
uri = uri.replaceFirst(";" + sessionURIParamName + "=[^&?]*", "");
}
String queryString = request.getQueryString();
if (null != queryString) {
uri = uri + "?" + queryString;
}
// NOTE: Do not call response.encodeRedirectURL or the bad
// sessionid will be restored
response.setHeader("Location", uri);
response.setStatus(_redirectStatusCode);
} else {
getNext().invoke(request, response);
}
}
}

View File

@@ -0,0 +1,145 @@
# 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.
accessLogValve.alreadyExists=Failed to rename access log from [{0}] to [{1}], file already exists.
accessLogValve.closeFail=Failed to close access log file
accessLogValve.deleteFail=Failed to delete old access log [{0}]
accessLogValve.invalidLocale=Failed to set locale to [{0}]
accessLogValve.invalidPortType=Invalid port type [{0}], using server (local) port
accessLogValve.openDirFail=Failed to create directory [{0}] for access logs
accessLogValve.openFail=Failed to open access log file [{0}]
accessLogValve.renameFail=Failed to rename access log from [{0}] to [{1}]
accessLogValve.rotateFail=Failed to rotate access log
accessLogValve.unsupportedEncoding=Failed to set encoding to [{0}], will use the system default character set.
accessLogValve.writeFail=Failed to write log message [{0}]
# Default error page should not have '[' ']' symbols around substituted text fragments.
# https://bz.apache.org/bugzilla/show_bug.cgi?id=61134
errorReportValve.description=Description
errorReportValve.exception=Exception
errorReportValve.exceptionReport=Exception Report
errorReportValve.message=Message
errorReportValve.noDescription=No description available
errorReportValve.note=Note
errorReportValve.rootCause=Root Cause
errorReportValve.rootCauseInLogs=The full stack trace of the root cause is available in the server logs.
errorReportValve.statusHeader=HTTP Status {0} {1}
errorReportValve.statusReport=Status Report
errorReportValve.type=Type
errorReportValve.unknownReason=Unknown Reason
extendedAccessLogValve.badXParam=Invalid x parameter format, needs to be 'x-#(...)
extendedAccessLogValve.badXParamValue=Invalid x parameter value for Servlet request [{0}]
extendedAccessLogValve.decodeError=Unable to decode the rest of chars starting with [{0}]
extendedAccessLogValve.emptyPattern=Pattern was just empty or whitespace
extendedAccessLogValve.noClosing=No closing ) found for in decode
extendedAccessLogValve.patternParseError=Error parsing pattern [{0}]
http.400.desc=The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
http.400.reason=Bad Request
http.401.desc=The request has not been applied because it lacks valid authentication credentials for the target resource.
http.401.reason=Unauthorized
http.402.desc=This status code is reserved for future use.
http.402.reason=Payment Required
http.403.desc=The server understood the request but refuses to authorize it.
http.403.reason=Forbidden
http.404.desc=The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
http.404.reason=Not Found
http.405.desc=The method received in the request-line is known by the origin server but not supported by the target resource.
http.405.reason=Method Not Allowed
http.406.desc=The target resource does not have a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields received in the request, and the server is unwilling to supply a default representation.
http.406.reason=Not Acceptable
http.407.desc=This status code is similar to 401 (Unauthorized), but it indicates that the client needs to authenticate itself in order to use a proxy.
http.407.reason=Proxy Authentication Required
http.408.desc=The server did not receive a complete request message within the time that it was prepared to wait.
http.408.reason=Request Timeout
http.409.desc=The request could not be completed due to a conflict with the current state of the target resource.
http.409.reason=Conflict
http.410.desc=Access to the target resource is no longer available at the origin server and that this condition is likely to be permanent.
http.410.reason=Gone
http.411.desc=The server refuses to accept the request without a defined Content-Length.
http.411.reason=Length Required
http.412.desc=One or more conditions given in the request header fields evaluated to false when tested on the server.
http.412.reason=Precondition Failed
http.413.desc=The server is refusing to process a request because the request payload is larger than the server is willing or able to process.
http.413.reason=Payload Too Large
http.414.desc=The server is refusing to service the request because the request-target is longer than the server is willing to interpret.
http.414.reason=URI Too Long
http.415.desc=The origin server is refusing to service the request because the payload is in a format not supported by this method on the target resource.
http.415.reason=Unsupported Media Type
http.416.desc=None of the ranges in the request's Range header field overlap the current extent of the selected resource or that the set of ranges requested has been rejected due to invalid ranges or an excessive request of small or overlapping ranges.
http.416.reason=Range Not Satisfiable
http.417.desc=The expectation given in the request's Expect header field could not be met by at least one of the inbound servers.
http.417.reason=Expectation Failed
http.421.desc=The request was directed at a server that is not able to produce a response.
http.421.reason=Misdirected Request
http.422.desc=The server understands the content type of the request entity, and the syntax of the request entity is correct but was unable to process the contained instructions.
http.422.reason=Unprocessable Entity
http.423.desc=The source or destination resource of a method is locked.
http.423.reason=Locked
http.424.desc=The method could not be performed on the resource because the requested action depended on another action and that action failed.
http.424.reason=Failed Dependency
http.426.desc=the server refuses to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol.
http.426.reason=Upgrade Required
http.428.desc=The origin server requires the request to be conditional.
http.428.reason=Precondition Required
http.429.desc=The user has sent too many requests in a given amount of time ("rate limiting").
http.429.reason=Too Many Requests
http.431.desc=The server is unwilling to process the request because its header fields are too large.
http.431.reason=Request Header Fields Too Large
http.451.desc=The server refused this request for legal reasons.
http.451.reason=Unavailable For Legal Reasons
http.500.desc=The server encountered an unexpected condition that prevented it from fulfilling the request.
http.500.reason=Internal Server Error
http.501.desc=The server does not support the functionality required to fulfill the request.
http.501.reason=Not Implemented
http.502.desc=The server, while acting as a gateway or proxy, received an invalid response from an inbound server it accessed while attempting to fulfill the request.
http.502.reason=Bad Gateway
http.503.desc=The server is currently unable to handle the request due to a temporary overload or scheduled maintenance, which will likely be alleviated after some delay.
http.503.reason=Service Unavailable
http.504.desc=The server, while acting as a gateway or proxy, did not receive a timely response from an upstream server it needed to access in order to complete the request.
http.504.reason=Gateway Timeout
http.505.desc=The server does not support, or refuses to support, the major version of HTTP that was used in the request message.
http.505.reason=HTTP Version Not Supported
http.506.desc=The server has an internal configuration error: the chosen variant resource is configured to engage in transparent content negotiation itself, and is therefore not a proper end point in the negotiation process.
http.506.reason=Variant Also Negotiates
http.507.desc=The method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request.
http.507.reason=Insufficient Storage
http.508.desc=The server terminated an operation because it encountered an infinite loop while processing a request with "Depth: infinity".
http.508.reason=Loop Detected
http.510.desc=The policy for accessing the resource has not been met in the request
http.510.reason=Not Extended
http.511.desc=The client needs to authenticate to gain network access.
http.511.reason=Network Authentication Required
jdbcAccessLogValve.close=Failed to close database
jdbcAccessLogValve.exception=Exception performing insert access entry
remoteCidrValve.invalid=Invalid configuration provided for [{0}]. See previous messages for details.
remoteCidrValve.noRemoteIp=Client does not have an IP address. Request denied.
remoteIpValve.invalidHostHeader=Invalid value [{0}] found for Host in HTTP header [{1}]
remoteIpValve.invalidHostWithPort=Host value [{0}] in HTTP header [{1}] included a port number which will be ignored
remoteIpValve.invalidPortHeader=Invalid value [{0}] found for port in HTTP header [{1}]
requestFilterValve.configInvalid=One or more invalid configuration settings were provided for the Remote[Addr|Host]Valve which prevented the Valve and its parent containers from starting
requestFilterValve.deny=Denied request for [{0}] based on property [{1}]
sslValve.certError=Failed to process certificate string [{0}] to create a java.security.cert.X509Certificate object
sslValve.invalidProvider=The SSL provider specified on the connector associated with this request of [{0}] is invalid. The certificate data could not be processed.
stuckThreadDetectionValve.notifyStuckThreadCompleted=Thread [{0}] (id=[{3}]) was previously reported to be stuck but has completed. It was active for approximately [{1}] milliseconds.{2,choice,0#|0< There is/are still [{2}] thread(s) that are monitored by this Valve and may be stuck.}
stuckThreadDetectionValve.notifyStuckThreadDetected=Thread [{0}] (id=[{6}]) has been active for [{1}] milliseconds (since [{2}]) to serve the same request for [{4}] and may be stuck (configured threshold for this StuckThreadDetectionValve is [{5}] seconds). There is/are [{3}] thread(s) in total that are monitored by this Valve and may be stuck.
stuckThreadDetectionValve.notifyStuckThreadInterrupted=Thread [{0}] (id=[{5}]) has been interrupted because it was active for [{1}] milliseconds (since [{2}]) to serve the same request for [{3}] and was probably stuck (configured interruption threshold for this StuckThreadDetectionValve is [{4}] seconds).

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.
accessLogValve.invalidLocale=Konnte Locale nicht auf [{0}] setzen
accessLogValve.openFail=Konnte Access Logfile [{0}] nicht öffnen
accessLogValve.rotateFail=Rotieren des Zugriffslogs ist fehlgeschlagen
errorReportValve.description=Beschreibung
errorReportValve.note=Hinweis
errorReportValve.rootCauseInLogs=Der komplette Stacktrace der Ursache ist in den Server logs zu finden
errorReportValve.unknownReason=Unbekannter Grund
http.401.reason=Unautorisiert
http.402.desc=Dieser Status-Code ist für eine zukünftige Nutzung reserviert
http.403.desc=Der Server hat die Anfrage verstanden, verbietet aber eine Autoriesierung.
http.404.reason=nicht gefunden
http.406.reason=Nicht annehmbar
http.407.reason=Authentisierung für Proxy benötigt
http.411.reason=Länge benötigt
http.412.reason=Vorbedingung nicht erfüllt
http.415.reason=Nicht unterstützter Media-Type
http.421.desc=Die Anfrage wurde an einen Server gestellt der keine Antwort erzeugen konnte.
http.423.desc=Die Quell- oder Zielressource einer Methode ist gesperrt.
http.423.reason=Gesperrt
http.426.reason=Upgrade nötig
http.429.reason=Zu viele Anfragen
http.504.reason=Gateway-Zeitüberschreitung
http.505.reason=HTTP Version nicht unterstützt
http.507.reason=Nicht genügend Speicherplatz
http.510.reason=Nicht erweitert
http.511.desc=Um Netzwerk Zugriff zu erlangen muss sich der Client authentifizieren.
remoteCidrValve.noRemoteIp=Client verfügt über keine IP Adresse. Zugriff verweigert.
remoteIpValve.invalidPortHeader=Ungültiger Wert [{0}] für Port im HTTP Header [{1}] gefunden

View File

@@ -0,0 +1,85 @@
# 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.
accessLogValve.closeFail=No pude cerrar fichero de historial
accessLogValve.invalidLocale=Fallo al fijar locales a [{0}]\n
accessLogValve.openDirFail=No pude crear directorio [{0}] para historiales de acceso
accessLogValve.openFail=Fallo al abrir el archivo access log [{0}]\n
accessLogValve.rotateFail=No se pudo rotar el historial de acceso
errorReportValve.description=Descripción
errorReportValve.exception=excepción
errorReportValve.exceptionReport=Informe de Excepción
errorReportValve.message=mensaje
errorReportValve.note=nota
errorReportValve.rootCause=causa raíz
errorReportValve.rootCauseInLogs=La traza completa de la causa de este error se encuentra en los archivos de registro del servidor.
errorReportValve.statusHeader=Estado HTTP {0} {1}
errorReportValve.statusReport=Informe de estado
errorReportValve.type=Tipo
http.400.desc=El requerimiento enviado por el cliente era sintácticamente incorrecto.
http.401.desc=Este requerimiento requiere autenticación HTTP.
http.401.reason=No autorizado
http.402.desc=Este código de estado está reservado para uso futuro.
http.403.desc=El acceso al recurso especificado ha sido prohibido.
http.404.desc=El recurso requerido no está disponible.
http.404.reason=No encontrado
http.405.desc=El método HTTP especificado no está permitido para el recurso requerido.
http.406.desc=El recurso identificado por este requerimiento sólo es capaz de generar respuestas con características no aceptables con arreglo a las cabeceras "accept" de requerimiento.
http.407.desc=El cliente debe de ser primero autenticado en el apoderado.
http.407.reason=Se requiere autenticación de proxy
http.408.desc=El cliente no produjo un requerimiento dentro del tiempo en que el servidor estaba preparado esperando.
http.409.desc=El requerimiento no pudo ser completado debido a un conflicto con el estado actual del recurso.
http.410.desc=El recurso requerido ya no está disponible y no se conoce dirección de reenvío.
http.411.desc=Este requerimiento no puede ser manejado sin un tamaño definido de contenido.
http.412.desc=Una precondición especificada ha fallado para este requerimiento.
http.412.reason=La precon
http.413.desc=La entidad de requerimiento es mayor de lo que el servidor quiere o puede procesar.
http.414.desc=El servidor rechazó este requerimiento porque la URI requerida era demasiado larga.
http.415.desc=El servidor rechazó este requerimiento porque la entidad requerida se encuentra en un formato no soportado por el recurso requerido para el método requerido.
http.415.reason=Tipo de medio no soportado
http.416.desc=El rango de byte requerido no puede ser satisfecho.
http.417.desc=Lo que se espera dado por la cabecera "Expect" de requerimiento no pudo ser completado.
http.421.desc=La solicitud ha sido dirigida a un servidor que no fue capaz de producir una respuesta
http.422.desc=El servidor entendió el tipo de contenido y la sintáxis del requerimiento pero no pudo procesar las instrucciones contenidas.
http.423.desc=La fuente o recurso de destino de un método está bloqueada.
http.423.reason=Bloqueado
http.426.reason=Se requiere actualización
http.428.desc=El servidor de origen requiere que la petición sea condicional
http.429.reason=Demasiadas peticiones
http.431.reason=Los campos de cabecera solicitados son muy largos
http.500.desc=El servidor encontró un error interno que hizo que no pudiera rellenar este requerimiento.
http.501.desc=El servidor no soporta la funcionalidad necesaria para rellenar este requerimiento.
http.502.desc=Este servidor recibió una respuesta inválida desde un servidor que consultó cuando actuaba como apoderado o pasarela.
http.503.desc=El servicio requerido no está disponible en este momento.
http.504.desc=El servidor recibió un Tiempo Agotado desde un servidor superior cuando actuaba como pasarela o apoderado.
http.505.desc=El servidor no soporta la versión de protocolo HTTP requerida.
http.505.reason=Versión HTTP no soportada
http.507.desc=El recurso no tiene espacio suficiente para registrar el estado del recurso tras la ejecución de este método.
http.507.reason=El storage no es suficiente
http.511.desc=El cliente se tiene que autenticar para tener accesso a la red
jdbcAccessLogValve.exception=Excepción realizando entrada de acceso a inserción
remoteIpValve.invalidPortHeader=Valor inválido [{0}] hallado para el puerto en cabecera HTTP [{1}]
requestFilterValve.configInvalid=Uno o más parámetros de configuración inválidos fueron proveídos para Remote[Addr|Host]Valve lo cual impide que el Valve y sus contenedores padres puedan iniciar
sslValve.certError=No pude procesar cadena de certificado [{0}] para crear un objeto java.security.cert.X509Certificate
sslValve.invalidProvider=El proveedor de SSL especificado en el conecto asociado con este requerimiento de [{0}] ies inválido. No se pueden procesar los datos del certificado.
stuckThreadDetectionValve.notifyStuckThreadCompleted=El hilo [{0}] (id=[{3}]), que previamente se reportó como atascado, se ha completado. Estuvo activo por aproximadamente [{1}] milisegundos. {2, choice,0#|0< Hay aún [{2}] hilo(s) que son monitorizados por esta Válvula y pueden estar atascados.}
stuckThreadDetectionValve.notifyStuckThreadDetected=El hilo [{0}] (id=[{6}]) ha estado activo durante [{1}] miilisegundos (desde [{2}]) para servir el mismo requerimiento para [{4}] y puede estar atascado (el umbral configurado para este StuckThreadDetectionValve es de [{5}] segundos). Hay [{3}] hilo(s) en total que son monitorizados por esta Válvula y pueden estar atascados.

View File

@@ -0,0 +1,143 @@
# 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.
accessLogValve.alreadyExists=Échec de renommage du journal d''accès de [{0}] en [{1}], le fichier existe déjà.
accessLogValve.closeFail=Échec de fermeture du fichier de journal d'accès
accessLogValve.deleteFail=Impossible d''effacer l''ancien journal d''accès [{0}]
accessLogValve.invalidLocale=Impossible de définir les paramètres régionaux sur [{0}]
accessLogValve.invalidPortType=Type de port [{0}] invalide, utilisation du port (local) de serveur
accessLogValve.openDirFail=Echec de création du répertoire [{0}] pour les journaux d''accès
accessLogValve.openFail=Echec à l''ouverture du journal d''accès [{0}]
accessLogValve.renameFail=Échec de renommage du journal d''accès de [{0}] en [{1}]
accessLogValve.rotateFail=Échec de rotation des journaux d'accès
accessLogValve.unsupportedEncoding=Impossible de changer l''encodage en [{0}], le jeu de caractères par défaut du système sera utilisé
accessLogValve.writeFail=Impossible d''écrire le message de log [{0}]
errorReportValve.description=description
errorReportValve.exception=exception
errorReportValve.exceptionReport=Rapport d'exception
errorReportValve.message=message
errorReportValve.noDescription=Pas de description disponible
errorReportValve.note=note
errorReportValve.rootCause=cause mère
errorReportValve.rootCauseInLogs=La trace complète de la cause mère de cette erreur est disponible dans les fichiers journaux de ce serveur.
errorReportValve.statusHeader=État HTTP {0} {1}
errorReportValve.statusReport=Rapport d'état
errorReportValve.type=Type
errorReportValve.unknownReason=Raison inconnue.
extendedAccessLogValve.badXParam=Le format du paramètre étendu est invalide, il doit être de la forme 'x-#(...)'
extendedAccessLogValve.badXParamValue=La valeur du paramètre étendu est invalide pour la requête de Servlet [{0}]
extendedAccessLogValve.decodeError=Impossible de décoder les caractères restants à partir de [{0}]
extendedAccessLogValve.emptyPattern=Le modèle est vide
extendedAccessLogValve.noClosing=Une parenthèse de fermeture n'a pas été trouvée lors du décodage
extendedAccessLogValve.patternParseError=Erreur lors de l''analyse du modèle [{0}]
http.400.desc=La requête envoyée par le client était syntaxiquement incorrecte.
http.400.reason=Requête invalide
http.401.desc=La requête nécessite une authentification HTTP.
http.401.reason=Non authorisé
http.402.desc=Un paiement est demandé pour accéder à cette ressource.
http.402.reason=Paiement requis
http.403.desc=L'accès à la ressource demandée a été interdit.
http.403.reason=Interdit
http.404.desc=La ressource demandée n'est pas disponible.
http.404.reason=Non trouvé
http.405.desc=La méthode HTTP spécifiée n'est pas autorisée pour la ressource demandée.
http.405.reason=Méthode non autorisée
http.406.desc=La ressource identifiée par cette requête n'est capable de générer des réponses qu'avec des caractéristiques incompatible avec la directive "accept" présente dans l'entête de requête.
http.406.reason=Inacceptable
http.407.desc=Le client doit d'abord s'authentifier auprès du relais.
http.407.reason=Authentification Proxy est requise
http.408.desc=Le client n'a pas produit de requête pendant le temps d'attente du serveur.
http.408.reason=Timeout de la requête
http.409.desc=La requête ne peut être finalisée suite à un conflit lié à l'état de la ressource.
http.409.reason=Conflit
http.410.desc=La ressource demandée n'est pas disponible, et aucune adresse de rebond (forwarding) n'est connue.
http.410.reason=Disparu
http.411.desc=La requête ne peut être traitée sans définition d'une taille de contenu (content length).
http.411.reason=Une longueur est requise
http.412.desc=Une condition préalable demandée n'est pas satisfaite pour cette requête.
http.412.reason=Erreur dans la pré-condition
http.413.desc=L'entité de requête est plus importante que ce que le serveur veut ou peut traiter.
http.413.reason=Les données sont trop grandes
http.414.desc=Le serveur a refusé cette requête car l'URI de requête est trop longue.
http.414.reason=L'URI est trop longue
http.415.desc=Le serveur a refusé cette requête car l'entité de requête est dans un format non supporté par la ressource demandée avec la méthode spécifiée.
http.415.reason=Type de média non supporté
http.416.desc=La plage d'octets demandée (byte range) ne peut être satisfaite.
http.416.reason=Plage non réalisable
http.417.desc=L'attente indiquée dans la directive "Expect" de l'entête de requête ne peut être satisfaite.
http.417.reason=L'expectation a échouée
http.421.desc=La requête a été dirigée vers un serveur qui est incapable de produire une réponse.
http.421.reason=Requête mal dirigée
http.422.desc=Le serveur a compris le type de contenu (content type) ainsi que la syntaxe de la requête mais a été incapable de traiter les instructions contenues.
http.422.reason=Impossible de traiter cette entité
http.423.desc=La ressource source ou destination de la méthode est verrouillée.
http.423.reason=Verrouillé
http.424.desc=La méthode n'a pas pu être exécutée sur la ressource parce qu'elle dépendait d'une autre action qui a échouée
http.424.reason=Echec de dépendence
http.426.desc=Le serveur a refusé de traiter cette requête en utilisant le protocole actuel mais pourrait le faire si le client en utilise un autre
http.426.reason=Mise à jour du protocole requise
http.428.desc=Le serveur d'origine exige que la requête soit conditionnelle
http.428.reason=Précondition requise
http.429.desc=L'utilisateur a effectué une nombre de requêtes trop élevé dans un laps de temps trop court (limitation de fréquence)
http.429.reason=Trop de requêtes
http.431.desc=Le serveur refuse de traiter la requête parce que ses champs d'en-tête sont trop gros
http.431.reason=Les champs d'en-tête de la requête sont trop gros
http.451.desc=Le serveur a refusé cette requête pour des raisons légales
http.451.reason=Indisponible pour des raisons légales
http.500.desc=Le serveur a rencontré une erreur interne qui l'a empêché de satisfaire la requête.
http.500.reason=Erreur interne du serveur
http.501.desc=Le serveur ne supporte pas la fonctionnalité demandée pour satisfaire cette requête.
http.501.reason=Non implémentée
http.502.desc=Le serveur a reçu une réponse invalide d'un serveur qu'il consultait en tant que relais ou passerelle.
http.502.reason=Mauvaise passerelle
http.503.desc=Le service demandé n'est pas disponible actuellement.
http.503.reason=Service indisponible
http.504.desc=Le serveur a reçu un dépassement de délai (timeout) d'un serveur amont qu'il consultait en tant que relais ou passerelle.
http.504.reason=Timeout de la passerelle
http.505.desc=Le serveur ne supporte pas la version demandée du protocole HTTP.
http.505.reason=Version HTTP non supportée
http.506.desc=Le serveur a rencontré une erreur de configuration interne: la variante choisie de la ressource est configurée pour mener elle-même la négociation de contenu de manière transparente, et n'est donc pas le bon endroit pour la négociation elle-même
http.506.reason=506 Variant Also Negotiates (RFC 2295) (référence circulaire)
http.507.desc=L'espace disponible est insuffisant pour enregistrer l'état de la ressource après exécution de cette méthode.
http.507.reason=Stockage insuffisant
http.508.desc=Le serveur a mis fin à une opération car il a rencontré une boucle infinie en traitant une requête avec "Depth: infinity"
http.508.reason=Boucle détectée
http.510.desc=La requête ne correspond pas à la politique d'accès pour cette ressource
http.510.reason=Non étendu
http.511.desc=Le client doit s'authentifier pour accéder au réseau.
http.511.reason=Lauthentification du réseau est nécessaire
jdbcAccessLogValve.close=Echec de fermeture de la base de donnée
jdbcAccessLogValve.exception=Exception en insérant l'entrée de l'accès
remoteCidrValve.invalid=La configuration fournie pour [{0}] est invalide, voir les précédents messages pour plus de détails
remoteCidrValve.noRemoteIp=Le client n'a pas d'adresse IP, requête interdite
remoteIpValve.invalidHostHeader=La valeur invalide [{0}] a été trouvée pour le Host dans l''en-tête HTTP [{1}]
remoteIpValve.invalidHostWithPort=La valeur de Host [{0}] dans l''en-tête HTTP [{1}] contenait un numéro de port qui sera ingnoré
remoteIpValve.invalidPortHeader=La valeur de port [{0}] trouvée dans l''en-tête HTTP [{1}] est invalide
requestFilterValve.configInvalid=Un ou plusieurs paramètres de configuration spécifiés pour ce Remote[Addr|Host]Valve ont empêché la Valve et le conteneur parent de démarrer
requestFilterValve.deny=Refus de la requête pour [{0}] basé sur la propriété [{1}]
sslValve.certError=Impossible de traiter le certificat [{0}] pour créer un objet java.security.cert.X509Certificate
sslValve.invalidProvider=Le fournisseur SSL spécifié pour le connecteur associé avec cette requête de [{0}] est invalide, le certificat n''a pas pu être traité
stuckThreadDetectionValve.notifyStuckThreadCompleted=Le Thread [{0}] (id=[{3}]) qui a été préalablement rapporté comme étant bloqué s''est terminé, il a été actif pendant approximativement [{1}] millisecondes, il y a [{2}] thread(s) au total qui sont surveillés par cette valve et qui pourraient être bloqués
stuckThreadDetectionValve.notifyStuckThreadDetected=Le Thread [{0}] (id=[{6}]) a été actif depuis [{1}] millisecondes (depuis [{2}]) pour traiter la même requête pour [{4}] et pourrait être bloqué (le seuil configurable est de [{5}] secondes pour cette StuckThreadDetectionValve), il y a [{3}] thread(s) au total qui sont surveillés par cette valve et qui pourraient être bloqués
stuckThreadDetectionValve.notifyStuckThreadInterrupted=Le Thread [{0}] (id=[{5}]) a été interrompu car il a été actif depuis [{1}] millisecondes (depuis [{2}]) pour traiter la même requête pour [{3}] et était probablement bloqué (le seuil configurable est de [{4}] secondes pour cette StuckThreadDetectionValve)

View File

@@ -0,0 +1,142 @@
# 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.
accessLogValve.alreadyExists=[{0}]から[{1}]へのアクセスログの名前の変更に失敗しました。ファイルはすでに存在しています。
accessLogValve.closeFail=アクセスログのクローズに失敗しました
accessLogValve.deleteFail=古いアクセスログ[{0}]を削除できませんでした。
accessLogValve.invalidLocale=[{0}] をロケールに設定できませんでした。
accessLogValve.invalidPortType=不正なポート種類 [{0}] の代わりにサーバーのローカルポートを使用します。
accessLogValve.openDirFail=アクセスログのディレクトリ[{0}]の作成に失敗しました
accessLogValve.openFail=アクセスログファイル [{0}] を開けません。
accessLogValve.renameFail=[{0}]から[{1}]へのアクセスログの名前の変更に失敗しました。
accessLogValve.rotateFail=アクセスログのローテーションに失敗しました
accessLogValve.unsupportedEncoding=文字エンコーディングに [{0}] を指定できません。システムの初期値を使用します。
accessLogValve.writeFail=ログメッセージ[{0}]の書き込みに失敗しました
errorReportValve.description=説明
errorReportValve.exception=例外
errorReportValve.exceptionReport=例外報告
errorReportValve.message=メッセージ
errorReportValve.noDescription=説明はありません
errorReportValve.note=注意
errorReportValve.rootCause=根本原因
errorReportValve.rootCauseInLogs=原因のすべてのスタックトレースは、サーバのログに記録されています
errorReportValve.statusHeader=HTTPステータス {0} {1}
errorReportValve.statusReport=ステータスレポート
errorReportValve.type=タイプ
errorReportValve.unknownReason=未知の理由
extendedAccessLogValve.badXParam=無効なxパラメータフォーマットです。 'x-#(...)にする必要があります。
extendedAccessLogValve.badXParamValue=サーブレットリクエスト[{0}]の無効なxパラメータ値
extendedAccessLogValve.decodeError=[{0}]で始まる残りの文字をデコードできません
extendedAccessLogValve.emptyPattern=パターン文字列が空です。もしくは空白だけで構成されています。
extendedAccessLogValve.noClosing=終了)がデコードで見つかりません。
extendedAccessLogValve.patternParseError=パターン文字列 [{0}] を解釈できませんでした。
http.400.desc=サーバは、クライアントエラー(例えば、不正なリクエスト構文、無効なリクエストメッセージフレーミング、または不正なリクエストルーティング)であると考えられるために、リクエストを処理できない、または処理しません。
http.400.reason=Bad Request
http.401.desc=リクエストには対象リソースの有効な認証資格がないため、適用されていません。
http.401.reason=Unauthorized
http.402.desc=このステータスコードは、将来の使用のために予約されています
http.402.reason=Payment Required
http.403.desc=サーバーはリクエストの認証を拒否しました。
http.403.reason=Forbidden
http.404.desc=オリジンサーバーは、ターゲットリソースの現在の表現を見つけられなかったか、またはそれが存在することを開示するつもりはありません。
http.404.reason=見つかりません。
http.405.desc=リクエストラインで受信されたメソッドは、オリジンサーバーによって認識されますが、ターゲットリソースによってサポートされていません。
http.405.reason=Method Not Allowed
http.406.desc=ターゲットリソースは、リクエストで受け取ったプロアクティブなネゴシエーションヘッダフィールドに従って、ユーザエージェントが受け入れられる現在の表現を持たず、サーバはデフォルトの表現を提供することを望ましくありません。
http.406.reason=Not Acceptable
http.407.desc=このステータスコードは401Unauthorizedに似ていますが、クライアントがプロキシを使用するために自身を認証する必要があることを示します。
http.407.reason=プロキシ認証が必要です。
http.408.desc=サーバーは、待機用に準備された時間内に完全なリクエストメッセージを受信しませんでした。
http.408.reason=リクエストタイムアウト
http.409.desc=ターゲットリソースの現在の状態との競合のためにリクエストを完了できませんでした。
http.409.reason=Conflict
http.410.desc=オリジンサーバーでターゲットリソースへのアクセスが利用できなくなり、この状態が永続的になる可能性があります。
http.410.reason=Gone
http.411.desc=サーバーは、定義されたContent-Lengthなしでリクエストを受け入れることを拒否します。
http.411.reason=Length Required
http.412.desc=リクエストヘッダーフィールドに指定された1つ以上の条件が、サーバー上でテストされたときにfalseに評価されました。
http.412.reason=前提条件失敗
http.413.desc=リクエストペイロードがサーバーが処理できる、または処理できるよりも大きいため、サーバーはリクエストの処理を拒否しています。
http.413.reason=\ Payload Too Large
http.414.desc=リクエストの対象がサーバーが解釈しようとするよりも長いため、サーバーはリクエストのサービスを拒否しています。
http.414.reason=URI Too Long
http.415.desc=ペイロードがターゲットリソース上のこのメソッドでサポートされていない形式であるため、オリジンサーバーはリクエストを処理することを拒否しています。
http.415.reason=未対応のメディアタイプです。
http.416.desc=リクエストのRangeヘッダーフィールドの範囲のいずれも、選択されたリソースの現在のエクステントと重複しないか、無効な範囲または小さすぎる範囲または重複する範囲の過剰なリクエストのためにリクエストされた範囲の集合が拒否されました。
http.416.reason=Range Not Satisfiable
http.417.desc=リクエストのExpectヘッダーフィールドで指定された期待値が、少なくとも1つのインバウンドサーバーで満たされていない可能性があります。
http.417.reason=Expectation Failed
http.421.desc=リクエストはレスポンスを生成できないサーバーに向けられました。
http.421.reason=Misdirected Request
http.422.desc=サーバーはリクエストエンティティのコンテンツタイプを理解しており、リクエストエンティティの構文は正しいものの、含まれている命令を処理できませんでした。
http.422.reason=Unprocessable Entity
http.423.desc=メソッド呼び出しの依頼元リソース、あるいは依頼先リソースはロックされています。
http.423.reason=ロックされています
http.424.desc=要求されたアクションが別のアクションに依存し、そのアクションが失敗したため、このメソッドはリソース上で実行できませんでした。
http.424.reason=Failed Dependency
http.426.desc=サーバーは現在のプロトコルを使用してリクエストを実行することを拒否しますが、クライアントが別のプロトコルにアップグレードした後にその要求を実行する可能性があります。
http.426.reason=アップグレードが必要です。
http.428.desc=オリジンサーバーは、要求が条件付きであることを要求します。
http.428.reason=Precondition Required
http.429.desc=ユーザーが指定した時間内に多くのリクエストを送信しました(レート制限)。
http.429.reason=大量のリクエストが発生しています。
http.431.desc=ヘッダーフィールドが大きすぎるため、サーバーはリクエストをすすんで処理しません。
http.431.reason=リクエストヘッダフィールドが大き過ぎます。
http.451.desc=サーバーはこのリクエストを法的理由で拒否しました。
http.451.reason=Unavailable For Legal Reasons
http.500.desc=サーバーは予期しない条件に遭遇しました。それはリクエストの実行を妨げます。
http.500.reason=Internal Server Error
http.501.desc=サーバーは、リクエストを実行するために必要な機能をサポートしていません。
http.501.reason=Not Implemented
http.502.desc=ゲートウェイあるいはプロキシサーバーからリクエストを試みた内部サーバーから不正なレスポンスを受信しました。
http.502.reason=Bad Gateway
http.503.desc=サーバーは、一時的な過負荷または定期保守のために現在リクエストを処理できません。遅れて緩和される可能性があります。
http.503.reason=Service Unavailable
http.504.desc=ゲートウェイまたはプロキシとして機能しているサーバーは、リクエストを完了するためにアクセスする必要のある上流のサーバーからタイムリーなレスポンスを受信しませんでした。
http.504.reason=ゲートウェイタイムアウト
http.505.desc=サーバーは、リクエストメッセージで使用されたメジャーバージョンのHTTPをサポートしていないか、またはサポートを拒否しています。
http.505.reason=サポートされていないHTTPバージョン
http.506.desc=サーバーには内部構成エラーがあります。選択された異形のリソースは透過的なコンテンツネゴシエーション自体に関与するように構成されているため、ネゴシエーションプロセスの適切なエンドポイントではありません。
http.506.reason=Variant Also Negotiates
http.507.desc=サーバーがリクエストを正常に完了するのに必要な表現を保管できないため、このメソッドをリソースに対して実行できませんでした。
http.507.reason=ストレージに充分な空き容量がありません。
http.508.desc=サーバーは、 "Depthinfinity"でリクエストを処理している間に無限ループを検出したため、操作を終了しました。
http.508.reason=Loop Detected
http.510.desc=リクエストにリソースにアクセスするためのポリシーが満たされていません。
http.510.reason=Not Extended
http.511.desc=クライアントはネットワークアクセスを取得するために認証する必要があります。
http.511.reason=Network Authentication Required
jdbcAccessLogValve.close=データベースのクローズに失敗しました。
jdbcAccessLogValve.exception=アクセスエントリの挿入を実行中の例外です
remoteCidrValve.invalid="[{0}]" に不正な値が指定されました。詳細は前のメッセージを参照してください。
remoteCidrValve.noRemoteIp=クライアントの IP アドレスを取得できません。リクエストを拒否します。
remoteIpValve.invalidPortHeader=HTTP ヘッダー [{1}] に不正なポート番号 [{0}] が指定されました。
requestFilterValve.configInvalid=Valveとその親コンテナの起動を妨げたRemote [Addr | Host] Valveに1つ以上の無効な構成設定が提供されました。
requestFilterValve.deny=プロパティ [{1}] により [{0}] へのリクエストを拒否しました。
sslValve.certError=java.security.cert.X509Certificateオブジェクトを作成するための証明書文字列[{0}]を処理を失敗しました。
sslValve.invalidProvider=リクエスト [{0}] に関連付けられたコネクターへ不正な SSL プロバイダーが構成されています。証明書データを処理できません。
stuckThreadDetectionValve.notifyStuckThreadCompleted=スレッド[{0}]id = [{3}])は以前にスタックされていると報告されましたが完了しました。それはおよそ[{1}]ミリ秒の間アクティブだった。\n\
\ {2,choice,0#|0< このバルブによって監視されているスレッド [{2}] は残っていますが、スタックされている可能性があります。}
stuckThreadDetectionValve.notifyStuckThreadDetected=スレッド[{0}]ID = [{6}])は[{1}]ミリ秒([{2}以降)から[{4}]に対する同じリクエストを処理するためにアクティブであり、スタックされている可能性があります(このStuckThreadDetectionValveの設定されたしきい値(threshold )は[{5}]秒です。このValveによって監視されているスレッドは合計で[{3}]個あり、スタックされている可能性があります。
stuckThreadDetectionValve.notifyStuckThreadInterrupted=スレッド [{0}] (id=[{5}]) に割り込みが発生しました。[{3}] に対するリクエストの処理時間が [{1}] ミリ秒 ([{2}] から開始) を超過したため処理が進まなくなっている可能性があります。StuckThreadDetectionValve には割り込みが発生するまでの時間 [{4}] 秒が設定されています。

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.
accessLogValve.alreadyExists=접근 로그 파일을 [{0}]에서 [{1}](으)로 이름을 변경하지 못했습니다. 파일이 이미 존재합니다.
accessLogValve.closeFail=접근 로그 파일을 닫지 못했습니다.
accessLogValve.deleteFail=이전 접근 로그 파일 [{0}]을(를) 삭제하지 못했습니다.
accessLogValve.invalidLocale=로케일을 [{0}](으)로 설정하지 못했습니다.
accessLogValve.invalidPortType=유효하지 않은 포트 타입 [{0}]. 서버 (로컬) 포트를 사용합니다.
accessLogValve.openDirFail=접근 로그 파일(들)을 위한 디렉토리 [{0}]을(를) 생성하지 못했습니다.
accessLogValve.openFail=접근 로그 파일 [{0}]을(를) 열지 못했습니다.
accessLogValve.renameFail=접근 로그 파일을 [{0}]에서 [{1}](으)로 이름을 변경하지 못했습니다.
accessLogValve.rotateFail=접근 로그를 순환시키지 못했습니다.
accessLogValve.unsupportedEncoding=인코딩을 [{0}](으)로 설정하지 못했습니다. 시스템 기본 문자셋을 사용할 것입니다.
accessLogValve.writeFail=다음 로그 메시지를 쓰지 못했습니다: [{0}]
errorReportValve.description=설명
errorReportValve.exception=예외
errorReportValve.exceptionReport=예외 보고
errorReportValve.message=메시지
errorReportValve.noDescription=설명이 없습니다.
errorReportValve.note=비고
errorReportValve.rootCause=근본 원인 (root cause)
errorReportValve.rootCauseInLogs=근본 원인(root cause)의 풀 스택 트레이스를, 서버 로그들에서 확인할 수 있습니다.
errorReportValve.statusHeader=HTTP 상태 {0} {1}
errorReportValve.statusReport=상태 보고
errorReportValve.type=타입
errorReportValve.unknownReason=알 수 없는 사유
extendedAccessLogValve.badXParam=유효하지 않은 x 파라미터 포맷. 포맷은 'x-#(...) 이어야 합니다.
extendedAccessLogValve.badXParamValue=서블릿 요청을 위해 유효하지 않은 x 파라미터 값: [{0}]
extendedAccessLogValve.decodeError=[{0}](으)로 시작하는 문자들의 나머지 부분을 디코드할 수 없습니다.
extendedAccessLogValve.emptyPattern=패턴이 그저 빈 문자열이었거나, 공백 문자로만 채워진 문자열이었습니다.
extendedAccessLogValve.noClosing=디코드된 접근 로그 행에서 닫는 중괄호, '')'', 가 없습니다.
extendedAccessLogValve.patternParseError=패턴 [{0}]을(를) 파싱하는 중 오류 발생
http.400.desc=클라이언트 오류로서 인지된 어떤 문제로 인하여, 서버가 해당 요청을 처리할 수 없거나, 처리하지 않을 것입니다. (예: 잘못된 요청 문법, 유효하지 않은 요청 메시지 framing, 또는 신뢰할 수 없는 요청 라우팅).
http.400.reason=잘못된 요청
http.401.desc=대상 리소스에 접근하기 위한 유효한 인증 credentials가 없기 때문에, 요청에 적용되지 않았습니다.
http.401.reason=인가 안됨
http.402.desc=이 상태 코드는 미래의 사용을 위해 예약되어 있습니다.
http.402.reason=지불이 요구됨
http.403.desc=서버가 요청을 이해했으나 승인을 거부합니다.
http.403.reason=금지됨
http.404.desc=Origin 서버가 대상 리소스를 위한 현재의 representation을 찾지 못했거나, 그것이 존재하는지를 밝히려 하지 않습니다.
http.404.reason=찾을 수 없음
http.405.desc=요청 행에 포함된 해당 메소드는, origin 서버에 의해 인지되었으나, 대상 리소스에 의해 지원되지 않습니다.
http.405.reason=허용되지 않는 메소드
http.406.desc=요청으로부터 받은 proactive negotiation 헤더에 따르면, 대상 리소스는 해당 user agent가 받아들일만한 현재의 representation이 없고, 서버 또한 기본 representation을 제공하지 않으려 합니다.
http.406.reason=받아들일 수 없음
http.407.desc=이 상태 코드는 401 (인증 안됨)과 유사하나, 이는 클라이언트가 프록시를 사용하기 위하여 스스로를 인증할 필요가 있음을 알려줍니다.
http.407.reason=프록시 인증이 요구됨
http.408.desc=대기하도록 준비된 시간 이내에, 서버가 완전한 요청 메시지를 수신하지 못했습니다.
http.408.reason=요청 제한 시간 초과
http.409.desc=대상 리소스의 현재 상태와의 충돌 때문에, 요청이 완료될 수 없었습니다.
http.409.reason=충돌됨
http.410.desc=대상 리소스에 대한 접근이 해당 origin 서버에서 더이상 가용하지 않으며, 이러한 조건은 아마도 영구적일 것으로 보입니다.
http.410.reason=사라졌음
http.411.desc=Content-Length가 정의되지 않은 요청을, 서버가 받아들이기를 거부했습니다.
http.411.reason=Length가 요구됨
http.412.desc=서버에서 검사될 때, 요청 헤더 필드들 내에 주어진 하나 이상의 조건(들)이, false로 평가되었습니다.
http.412.reason=사전 조건 충족 실패
http.413.desc=요청의 payload가 서버가 처리하려 하거나 처리할 수 있는 것 보다 크기 때문에, 서버가 요청 처리를 거부합니다.
http.413.reason=Payload가 너무 큽니다.
http.414.desc=서버가 처리할 수 있는 것보다 request-target이 더 길기 때문에, 요청에 대한 서비스를 거부합니다.
http.414.reason=URI가 너무 깁니다.
http.415.desc=Payload가 대상 리소스에 대한 이 메소드에 의해 지원되지 않는 포맷이기 때문에, Origin 서버가 요청을 서비스하기를 거부합니다.
http.415.reason=지원되지 않는 Media Type
http.416.desc=요청의 Range 헤더 필드 내의 범위들 중 어느 것도, 선택된 리소스의 현재 범위와 겹치지 않거나, 요청된 범위들의 집합이 유효하지 않은 범위들, 또는 과도하게 작거나 겹치는 범위들이기 때문에 거절되었습니다.
http.416.reason=충족될 수 없는 범위
http.417.desc=요청의 Expect 헤더 필드에 주어진 expectation이, 적어도 하나 이상의 inbound 서버들에 의해 충족될 수 없었습니다.
http.417.reason=Expectation Failed
http.421.desc=요청이 응답을 생성할 수 없는 서버로 전달되었습니다.
http.421.reason=잘못 안내된 요청
http.422.desc=서버가 요청 엔티티의 Content-Type을 이해하고, 요청 엔티티의 문법이 올바르게 되어 있지만, 포함된 instruction들을 처리할 수 없었습니다.
http.422.reason=처리할 수 없는 엔티티
http.423.desc=메소드의 원본 또는 대상 리소스가 잠금 상태입니다.
http.423.reason=잠겨짐
http.424.desc=요청된 액션이 이미 실패한 또 다른 액션에 의존하고 있었기 때문에, 해당 리소스에 대해 이 메소드를 수행할 수 없습니다.
http.424.reason=실패한 의존적 요청
http.426.desc=서버가 현재의 프로토콜을 사용하여 요청을 처리하기를 거부했지만, 클라이언트가 다른 프로토콜로 업그레이드한 후에 처리하려 할 수도 있습니다.
http.426.reason=업그레이드가 요구됨
http.428.desc=Origin 서버는 요청이 사전 조건적이기를 요구합니다 (예: If-Match와 같은 헤더).
http.428.reason=사전조건이 필수적입니다.
http.429.desc=사용자가 주어진 시간 동안 너무 많은 요청을 보냈습니다. ("rate limiting")
http.429.reason=너무 많은 요청들
http.431.desc=요청 내의 헤더 필드들이 너무 커서 서버가 처리하려 하지 않습니다.
http.431.reason=요청의 헤더 필드들이 너무 큼
http.451.desc=서버가 법적인 사유들로 이 요청을 거부했습니다.
http.451.reason=법적인 사유들로 인하여 가용하지 않음
http.500.desc=서버가, 해당 요청을 충족시키지 못하게 하는 예기치 않은 조건을 맞닥뜨렸습니다.
http.500.reason=내부 서버 오류
http.501.desc=서버가 이 요청을 충족시키는데 필요한 필수적인 기능을 지원하지 않습니다.
http.501.reason=구현되지 않음
http.502.desc=서버가 게이트웨이 또는 프록시로서 동작하면서 요청을 처리하려 시도하는 동안, inbound 서버로부터 유효하지 않은 응답을 받았습니다.
http.502.reason=잘못된 게이트웨이
http.503.desc=일시적인 서버 부하 또는 예정된 유지보수 작업으로 인하여, 서버가 현재 요청을 처리할 수 없습니다. 잠시 지연된 뒤에 상황이 나아질 것으로 보입니다.
http.503.reason=서비스가 가용하지 않음
http.504.desc=서버가 게이트웨이 또는 프록시로 동작하는 동안, 요청을 처리 완료하기 위해 접근해야 하는 상위 서버로부터, 필요한 응답을 적절한 시간 내에 받지 못했습니다.
http.504.reason=게이트웨이 제한 시간 초과
http.505.desc=서버가 요청 메시지에서 사용된 HTTP의 major 버전을 지원하지 않거나, 또는 지원하기를 거부합니다.
http.505.reason=HTTP 버전이 지원되지 않음
http.506.desc=서버에 내부 설정 오류가 있습니다: 선택된 변형(variant) 리소스가, 투명한 컨텐트 교섭(negotiation) 그 자체에 관여하도록 설정되어 있는데, 그로 인하여 교섭 프로세스에 적절한 엔드포인트가 아닙니다.
http.506.reason=Variant Also Negotiates
http.507.desc=서버가 요청 처리를 성공적으로 완료하기 위해 필요한 representation을 저장할 수 없기 때문에, 해당 메소드가 해당 리소스에 대해 처리될 수 없었습니다.
http.507.reason=충분하지 않은 저장 공간
http.508.desc=서버가 "Depth: infinity"를 가진 요청을 처리하는 도중, 무한 루프를 맞닥뜨리는 바람에 오퍼레이션을 종료시켰습니다.
http.508.reason=루프가 탐지됨
http.510.desc=요청이, 리소스에 접근하기 위한 policy를 충족시키지 않습니다.
http.510.reason=확장 안됨
http.511.desc=클라이언트가 네트워크에 접근하기 위해서는 인증을 해야 합니다.
http.511.reason=네트워크 인증이 필요함
jdbcAccessLogValve.close=데이터베이스를 닫지 못했습니다.
jdbcAccessLogValve.exception=접근 엔트리를 추가하는 중 예외 발생
remoteCidrValve.invalid=[{0}]을(를) 위해 유효하지 않은 설정이 제공되었습니다. 상세 정보를 보시려면 이전 메시지들을 확인하십시오.
remoteCidrValve.noRemoteIp=클라이언트가 IP 주소를 가지고 있지 않습니다. 요청은 거절되었습니다.
remoteIpValve.invalidHostHeader=HTTP 헤더 [{1}] 내에 유효하지 않은 값이 발견되었습니다: [{0}]
remoteIpValve.invalidHostWithPort=HTTP 헤더 [{1}] 내의 호스트 값 [{0}]이(가) 포트 번호를 포함했는데, 이는 무시될 것입니다.
remoteIpValve.invalidPortHeader=HTTP 헤더 [{1}] 내에 유효하지 않은 포트 번호 값입니다: [{0}]
requestFilterValve.configInvalid=Remote[Addr|Host]Valve를 위해 하나 이상의 유효하지 않은 설정이 제공되었는데, 이는 해당 Valve와 부모 컨테이너들이 시작되지 못하게 했습니다.
requestFilterValve.deny=프로퍼티 [{1}]에 기반하여, [{0}]을(를) 위한 요청을 거절합니다.
sslValve.certError=java.security.cert.X509Certificate 객체를 생성하기 위한 인증서 문자열 [{0}]을(를) 처리하지 못했습니다.
sslValve.invalidProvider=[{0}]의 이 요청과 연관된 Connector에 지정된 SSL provider는 유효하지 않습니다. 해당 인증서 데이터가 처리될 수 없었습니다.
stuckThreadDetectionValve.notifyStuckThreadCompleted=쓰레드 [{0}] (ID=[{3}])이(가) 이전에 stuck 상태로 보고된 바 있으나 이제 완료되었습니다. 해당 쓰레드는 대략 [{1}] 밀리초 동안 활성화되어 있었습니다. {2,choice,0#|0< 이 Valve에 의해 모니터링되는 쓰레드들이 여전히 [{2}]개가 있고, 그것들은 어쩌면 stuck 상태에 있을 수 있습니다.}
stuckThreadDetectionValve.notifyStuckThreadDetected=쓰레드 [{0}] (id=[{6}])이(가), [{4}]을(를) 위한 동일한 요청을 처리하기 위해, ([{2}] 이후) [{1}] 밀리초 동안 활성화되어 있었으며, 해당 쓰레드가 stuck된 상태에 있을 수 있습니다.\n\
(이 StuckThreadDetectionValve를 위한 stuck 상태 진입 기준점은 [{5}] 초입니다.) 이 Valve에 의해 모니터되는 전체 쓰레드들 중 [{3}] 개의 쓰레드가 stuck 상태일 수 있습니다.
stuckThreadDetectionValve.notifyStuckThreadInterrupted=쓰레드 [{0}](id=[{5}])이(가), [{1}] 밀리초 동안 동일 요청을 처리하기 위해 ([{2}] 이후로) [{3}] 동안 활성화되어 있었으나, 필시 stuck 상태에 있을 법한 쓰레드이기 때문에 중단되었습니다. (이 StuckThreadDetectionValve를 위한 중단 한계치는 [{4}] 초로 설정되어 있습니다.)

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.
http.403.desc=Сервер получил запрос, но отказался его авторизовать.
http.404.reason=Не найдено
http.423.reason=Заблокирован
http.426.reason=Требуется обновление
http.429.reason=Слишком много запросов
http.511.desc=Клиенты должны пройти аутентификацию для получения доступа к сети.

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.
accessLogValve.closeFail=关闭访问日志文件失败
accessLogValve.invalidLocale=无法将区域设置设为 [{0}]
accessLogValve.invalidPortType=端口类型 [{0}] 无效,使用服务器(本地)端口
accessLogValve.openFail=无法打开访问日志文件[{0}]。
accessLogValve.rotateFail=失败的循环切割访问日志.
accessLogValve.writeFail=无法写入日志消息[{0}]
errorReportValve.description=描述
errorReportValve.exceptionReport=异常报告
errorReportValve.message=消息
errorReportValve.note=):注意
errorReportValve.rootCauseInLogs=主要问题的全部 stack 信息可以在 server logs 里查看
errorReportValve.unknownReason=未知的原因
http.400.desc=由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求。
http.400.reason=错误的请求
http.401.desc=因为当前请求缺少对目标资源对有效的认证信息,所以它不会实施。
http.401.reason=未经授权的
http.402.desc=这个状态码时为未来使用预留的.
http.402.reason=需要支付
http.403.desc=服务器理解该请求但拒绝授权。
http.403.reason=被禁止
http.404.desc=源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。
http.404.reason=未找到
http.405.desc=请求行中接收的方法由源服务器知道,但目标资源不支持
http.405.reason=方法不允许
http.406.desc=根据请求中接收到的主动协商头字段,目标资源没有用户代理可以接受的当前表示,而且服务器不愿意提供缺省表示。
http.406.reason=不可接收
http.407.desc=状态码和401未授权的类似但是表示客户端为了使用代理需要对它自身进行认证。
http.407.reason=代理需要认证
http.408.desc=在预分配的等待时间内,服务器未收到完整的请求信息。
http.408.reason=请求超时
http.409.desc=由于和目标资源对当前状态发生冲突,所以请求无法完成。
http.409.reason=冲突
http.410.desc=原始服务器上不再可以访问目标资源,并且此条件可能是永久性的。
http.411.reason=所需长度
http.412.desc=在服务器上测试时请求头字段中给出的一个或多个条件被评估为false。
http.412.reason=前置条件失败
http.413.reason=有效载荷过大
http.414.desc=服务器拒绝为请求提供服务,因为请求目标比服务器愿意解释的要长。
http.415.desc=源服务器拒绝服务请求,因为有效负载的格式在目标资源上此方法不支持。
http.415.reason=不支持的媒体类型
http.416.desc=(:请求的范围头字段中的任何范围都没有与选定资源的当前范围重叠,或者请求的范围集由于无效范围或小范围或重叠范围的过度请求而被拒绝。
http.416.reason=范围不满足
http.417.desc=(:至少有一个入站服务器无法满足请求的Expect头字段中给定的期望。
http.417.reason=期望的失败
http.421.desc=请求被定向到一台无法响应的服务器
http.423.desc=源或目标资源的方法被锁
http.423.reason=已锁定
http.424.desc=这个方法不能在这个资源上执行,因为请求操作依赖另一个操作,但是另一个操作失败了。
http.424.reason=失败的依赖项
http.426.desc=服务器拒绝使用当前协议执行请求,但可能愿意在客户端升级到其他协议后执行。
http.426.reason=需要升级
http.428.desc=原始服务器要求请求是有条件的。
http.429.reason=请求过多
http.431.reason=请求头的字段太大
http.451.desc=服务器出于法律原因拒绝了此请求。
http.500.desc=服务器遇到一个意外的情况,阻止它完成请求。
http.502.desc=服务器在充当网关或代理时, 在尝试完成请求时, 从它访问的入站服务器收到无效响应。
http.503.desc=由于临时过载或计划维护,服务器当前无法处理请求,这可能会在一些延迟后得到缓解。
http.504.desc=服务器在充当网关或代理时,没有从上游服务器接收到完成请求所需访问的及时响应。
http.504.reason=网关超时
http.505.reason=HTTP 版本不支持
http.506.desc=服务器内部配置错误:选取的变体资源配置为自身去处理透明的内容协商,因此在协商进程中不是一个合适的终点。
http.507.desc=无法对资源执行该方法,因为服务器无法存储成功完成请求所需的表示。
http.507.reason=存储空间.不足
http.510.reason=没有.扩展
http.511.desc=客户端需要进行身份验证才能获得网络访问权限。
remoteCidrValve.noRemoteIp=客户端没有IP地址。请求被拒绝。
remoteIpValve.invalidHostHeader=在HTTP请求头[{1}]中发现Host的无效值[{0}]
remoteIpValve.invalidPortHeader=HTTP标头[{1}]中的端口找到的值[{0}]无效
requestFilterValve.configInvalid=为Remote [Addr | Host]阀门提供了一个或多个无效配置设置阻止Valve及其父容器启动
requestFilterValve.deny=根据[{1}]配置拒绝[{0}]的请求
sslValve.invalidProvider=与此{[0}]请求关联的连接器上指定的SSL提供程序无效。 无法处理证书数据。
stuckThreadDetectionValve.notifyStuckThreadDetected=线程[{0}]id=[{6}])已处于活动状态[{1}]毫秒(自[{2}]起),以便为[{4}]提供相同的请求并且可能被卡住此StuckThreadDetectionValve的配置阈值为[{5}]秒)。总共有{3}个线程受此阀监视,可能被卡住。

View File

@@ -0,0 +1,233 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Globals;
import org.apache.catalina.Host;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.Store;
import org.apache.catalina.StoreManager;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
/**
* Valve that implements per-request session persistence. It is intended to be
* used with non-sticky load-balancers.
* <p>
* <b>USAGE CONSTRAINT</b>: To work correctly it requires a PersistentManager.
* <p>
* <b>USAGE CONSTRAINT</b>: To work correctly it assumes only one request exists
* per session at any one time.
*
* @author Jean-Frederic Clere
*/
public class PersistentValve extends ValveBase {
// Saves a couple of calls to getClassLoader() on every request. Under high
// load these calls took just long enough to appear as a hot spot (although
// a very minor one) in a profiler.
private static final ClassLoader MY_CLASSLOADER = PersistentValve.class.getClassLoader();
private volatile boolean clBindRequired;
//------------------------------------------------------ Constructor
public PersistentValve() {
super(true);
}
// --------------------------------------------------------- Public Methods
@Override
public void setContainer(Container container) {
super.setContainer(container);
if (container instanceof Engine || container instanceof Host) {
clBindRequired = true;
} else {
clBindRequired = false;
}
}
/**
* Select the appropriate child Context to process this request,
* based on the specified request URI. If no matching Context can
* be found, return an appropriate HTTP error.
*
* @param request Request to be processed
* @param response Response to be produced
*
* @exception IOException if an input/output error occurred
* @exception ServletException if a servlet error occurred
*/
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Context to be used for this Request
Context context = request.getContext();
if (context == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
// Update the session last access time for our session (if any)
String sessionId = request.getRequestedSessionId();
Manager manager = context.getManager();
if (sessionId != null && manager instanceof StoreManager) {
Store store = ((StoreManager) manager).getStore();
if (store != null) {
Session session = null;
try {
session = store.load(sessionId);
} catch (Exception e) {
container.getLogger().error("deserializeError");
}
if (session != null) {
if (!session.isValid() ||
isSessionStale(session, System.currentTimeMillis())) {
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("session swapped in is invalid or expired");
}
session.expire();
store.remove(sessionId);
} else {
session.setManager(manager);
// session.setId(sessionId); Only if new ???
manager.add(session);
// ((StandardSession)session).activate();
session.access();
session.endAccess();
}
}
}
}
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("sessionId: " + sessionId);
}
// Ask the next valve to process the request.
getNext().invoke(request, response);
// If still processing async, don't try to store the session
if (!request.isAsync()) {
// Read the sessionid after the response.
// HttpSession hsess = hreq.getSession(false);
Session hsess;
try {
hsess = request.getSessionInternal(false);
} catch (Exception ex) {
hsess = null;
}
String newsessionId = null;
if (hsess!=null) {
newsessionId = hsess.getIdInternal();
}
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("newsessionId: " + newsessionId);
}
if (newsessionId!=null) {
try {
bind(context);
/* store the session and remove it from the manager */
if (manager instanceof StoreManager) {
Session session = manager.findSession(newsessionId);
Store store = ((StoreManager) manager).getStore();
if (store != null && session != null && session.isValid() &&
!isSessionStale(session, System.currentTimeMillis())) {
store.save(session);
((StoreManager) manager).removeSuper(session);
session.recycle();
} else {
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("newsessionId store: " +
store + " session: " + session +
" valid: " +
(session == null ? "N/A" : Boolean.toString(
session.isValid())) +
" stale: " + isSessionStale(session,
System.currentTimeMillis()));
}
}
} else {
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("newsessionId Manager: " +
manager);
}
}
} finally {
unbind(context);
}
}
}
}
/**
* Indicate whether the session has been idle for longer
* than its expiration date as of the supplied time.
*
* FIXME: Probably belongs in the Session class.
* @param session The session to check
* @param timeNow The current time to check for
* @return <code>true</code> if the session is past its expiration
*/
protected boolean isSessionStale(Session session, long timeNow) {
if (session != null) {
int maxInactiveInterval = session.getMaxInactiveInterval();
if (maxInactiveInterval >= 0) {
int timeIdle = // Truncate, do not round up
(int) ((timeNow - session.getThisAccessedTime()) / 1000L);
if (timeIdle >= maxInactiveInterval) {
return true;
}
}
}
return false;
}
private void bind(Context context) {
if (clBindRequired) {
context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
}
}
private void unbind(Context context) {
if (clBindRequired) {
context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Concrete implementation of <code>RequestFilterValve</code> that filters
* based on the string representation of the remote client's IP address
* optionally combined with the server connector port number.
*
* @author Craig R. McClanahan
*/
public final class RemoteAddrValve extends RequestFilterValve {
private static final Log log = LogFactory.getLog(RemoteAddrValve.class);
// --------------------------------------------------------- Public Methods
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String property;
if (getAddConnectorPort()) {
property = request.getRequest().getRemoteAddr() + ";" + request.getConnector().getPort();
} else {
property = request.getRequest().getRemoteAddr();
}
process(property, request, response);
}
@Override
protected Log getLog() {
return log;
}
}

View File

@@ -0,0 +1,199 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.NetMask;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public final class RemoteCIDRValve extends ValveBase {
/**
* Our logger
*/
private static final Log log = LogFactory.getLog(RemoteCIDRValve.class);
/**
* The list of allowed {@link NetMask}s
*/
private final List<NetMask> allow = new ArrayList<>();
/**
* The list of denied {@link NetMask}s
*/
private final List<NetMask> deny = new ArrayList<>();
public RemoteCIDRValve() {
super(true);
}
/**
* Return a string representation of the {@link NetMask} list in #allow.
*
* @return the #allow list as a string, without the leading '[' and trailing
* ']'
*/
public String getAllow() {
return allow.toString().replace("[", "").replace("]", "");
}
/**
* Fill the #allow list with the list of netmasks provided as an argument,
* if any. Calls #fillFromInput.
*
* @param input The list of netmasks, as a comma separated string
* @throws IllegalArgumentException One or more netmasks are invalid
*/
public void setAllow(final String input) {
final List<String> messages = fillFromInput(input, allow);
if (messages.isEmpty()) {
return;
}
for (final String message : messages) {
log.error(message);
}
throw new IllegalArgumentException(sm.getString("remoteCidrValve.invalid", "allow"));
}
/**
* Return a string representation of the {@link NetMask} list in #deny.
*
* @return the #deny list as a string, without the leading '[' and trailing
* ']'
*/
public String getDeny() {
return deny.toString().replace("[", "").replace("]", "");
}
/**
* Fill the #deny list with the list of netmasks provided as an argument, if
* any. Calls #fillFromInput.
*
* @param input The list of netmasks, as a comma separated string
* @throws IllegalArgumentException One or more netmasks are invalid
*/
public void setDeny(final String input) {
final List<String> messages = fillFromInput(input, deny);
if (messages.isEmpty()) {
return;
}
for (final String message : messages) {
log.error(message);
}
throw new IllegalArgumentException(sm.getString("remoteCidrValve.invalid", "deny"));
}
@Override
public void invoke(final Request request, final Response response) throws IOException, ServletException {
if (isAllowed(request.getRequest().getRemoteAddr())) {
getNext().invoke(request, response);
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
private boolean isAllowed(final String property) {
final InetAddress addr;
try {
addr = InetAddress.getByName(property);
} catch (UnknownHostException e) {
// This should be in the 'could never happen' category but handle it
// to be safe.
log.error(sm.getString("remoteCidrValve.noRemoteIp"), e);
return false;
}
for (final NetMask nm : deny) {
if (nm.matches(addr)) {
return false;
}
}
for (final NetMask nm : allow) {
if (nm.matches(addr)) {
return true;
}
}
// Allow if deny is specified but allow isn't
if (!deny.isEmpty() && allow.isEmpty()) {
return true;
}
// Deny this request
return false;
}
/**
* Fill a {@link NetMask} list from a string input containing a
* comma-separated list of (hopefully valid) {@link NetMask}s.
*
* @param input The input string
* @param target The list to fill
* @return a string list of processing errors (empty when no errors)
*/
private List<String> fillFromInput(final String input, final List<NetMask> target) {
target.clear();
if (input == null || input.isEmpty()) {
return Collections.emptyList();
}
final List<String> messages = new LinkedList<>();
NetMask nm;
for (final String s : input.split("\\s*,\\s*")) {
try {
nm = new NetMask(s);
target.add(nm);
} catch (IllegalArgumentException e) {
messages.add(s + ": " + e.getMessage());
}
}
return Collections.unmodifiableList(messages);
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Concrete implementation of <code>RequestFilterValve</code> that filters
* based on the remote client's host name optionally combined with the
* server connector port number.
*
* @author Craig R. McClanahan
*/
public final class RemoteHostValve extends RequestFilterValve {
private static final Log log = LogFactory.getLog(RemoteHostValve.class);
// --------------------------------------------------------- Public Methods
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String property;
if (getAddConnectorPort()) {
property = request.getRequest().getRemoteHost() + ";" + request.getConnector().getPort();
} else {
property = request.getRequest().getRemoteHost();
}
process(property, request, response);
}
@Override
protected Log getLog() {
return log;
}
}

View File

@@ -0,0 +1,963 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import org.apache.catalina.AccessLog;
import org.apache.catalina.Globals;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.http.parser.Host;
/**
* <p>
* Tomcat port of <a href="https://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, this valve replaces the apparent
* client remote IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request
* headers (e.g. "X-Forwarded-For").
* </p>
* <p>
* Another feature of this valve is to replace the apparent scheme (http/https) and server port with the scheme presented by a proxy or a
* load balancer via a request header (e.g. "X-Forwarded-Proto").
* </p>
* <p>
* This valve proceeds as follows:
* </p>
* <p>
* If the incoming <code>request.getRemoteAddr()</code> matches the valve's list
* of internal or trusted proxies:
* </p>
* <ul>
* <li>Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer or proxy in the given request's Http
* header named <code>$remoteIpHeader</code> (default value <code>x-forwarded-for</code>). Values are processed in right-to-left order.</li>
* <li>For each ip/host of the list:
* <ul>
* <li>if it matches the internal proxies list, the ip/host is swallowed</li>
* <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li>
* <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li>
* </ul>
* </li>
* <li>If the request http header named <code>$protocolHeader</code> (e.g. <code>x-forwarded-proto</code>) consists only of forwards that match
* <code>protocolHeaderHttpsValue</code> configuration parameter (default <code>https</code>) then <code>request.isSecure = true</code>,
* <code>request.scheme = https</code> and <code>request.serverPort = 443</code>. Note that 443 can be overwritten with the
* <code>$httpsServerPort</code> configuration parameter.</li>
* <li>Mark the request with the attribute {@link Globals#REQUEST_FORWARDED_ATTRIBUTE} and value {@code Boolean.TRUE} to indicate
* that this request has been forwarded by one or more proxies.</li>
* </ul>
* <table border="1">
* <caption>Configuration parameters</caption>
* <tr>
* <th>RemoteIpValve property</th>
* <th>Description</th>
* <th>Equivalent mod_remoteip directive</th>
* <th>Format</th>
* <th>Default Value</th>
* </tr>
* <tr>
* <td>remoteIpHeader</td>
* <td>Name of the Http Header read by this valve that holds the list of traversed IP addresses starting from the requesting client</td>
* <td>RemoteIPHeader</td>
* <td>Compliant http header name</td>
* <td>x-forwarded-for</td>
* </tr>
* <tr>
* <td>internalProxies</td>
* <td>Regular expression that matches the IP addresses of internal proxies.
* If they appear in the <code>remoteIpHeader</code> value, they will be
* trusted and will not appear
* in the <code>proxiesHeader</code> value</td>
* <td>RemoteIPInternalProxy</td>
* <td>Regular expression (in the syntax supported by
* {@link java.util.regex.Pattern java.util.regex})</td>
* <td>10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|
* 169\.254\.\d{1,3}\.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|
* 172\.1[6-9]{1}\.\d{1,3}\.\d{1,3}|172\.2[0-9]{1}\.\d{1,3}\.\d{1,3}|
* 172\.3[0-1]{1}\.\d{1,3}\.\d{1,3}|
* 0:0:0:0:0:0:0:1|::1
* <br>
* By default, 10/8, 192.168/16, 169.254/16, 127/8, 172.16/12, and ::1 are allowed.</td>
* </tr>
* <tr>
* <td>proxiesHeader</td>
* <td>Name of the http header created by this valve to hold the list of proxies that have been processed in the incoming
* <code>remoteIpHeader</code></td>
* <td>proxiesHeader</td>
* <td>Compliant http header name</td>
* <td>x-forwarded-by</td>
* </tr>
* <tr>
* <td>trustedProxies</td>
* <td>Regular expression that matches the IP addresses of trusted proxies.
* If they appear in the <code>remoteIpHeader</code> value, they will be
* trusted and will appear in the <code>proxiesHeader</code> value</td>
* <td>RemoteIPTrustedProxy</td>
* <td>Regular expression (in the syntax supported by
* {@link java.util.regex.Pattern java.util.regex})</td>
* <td>&nbsp;</td>
* </tr>
* <tr>
* <td>protocolHeader</td>
* <td>Name of the http header read by this valve that holds the flag that this request </td>
* <td>N/A</td>
* <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code> or <code>Front-End-Https</code></td>
* <td><code>null</code></td>
* </tr>
* <tr>
* <td>protocolHeaderHttpsValue</td>
* <td>Value of the <code>protocolHeader</code> to indicate that it is an Https request</td>
* <td>N/A</td>
* <td>String like <code>https</code> or <code>ON</code></td>
* <td><code>https</code></td>
* </tr>
* <tr>
* <td>httpServerPort</td>
* <td>Value returned by {@link javax.servlet.ServletRequest#getServerPort()} when the <code>protocolHeader</code> indicates <code>http</code> protocol</td>
* <td>N/A</td>
* <td>integer</td>
* <td>80</td>
* </tr>
* <tr>
* <td>httpsServerPort</td>
* <td>Value returned by {@link javax.servlet.ServletRequest#getServerPort()} when the <code>protocolHeader</code> indicates <code>https</code> protocol</td>
* <td>N/A</td>
* <td>integer</td>
* <td>443</td>
* </tr>
* </table>
* <p>
* This Valve may be attached to any Container, depending on the granularity of the filtering you wish to perform.
* </p>
* <p>
* <strong>Regular expression vs. IP address blocks:</strong> <code>mod_remoteip</code> allows to use address blocks (e.g.
* <code>192.168/16</code>) to configure <code>RemoteIPInternalProxy</code> and <code>RemoteIPTrustedProxy</code> ; as Tomcat doesn't have a
* library similar to <a
* href="https://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#gb74d21b8898b7c40bf7fd07ad3eb993d">apr_ipsubnet_test</a>,
* <code>RemoteIpValve</code> uses regular expression to configure <code>internalProxies</code> and <code>trustedProxies</code> in the same
* fashion as {@link RequestFilterValve} does.
* </p>
* <hr>
* <p>
* <strong>Sample with internal proxies</strong>
* </p>
* <p>
* RemoteIpValve configuration:
* </p>
* <code>
* &lt;Valve
* className="org.apache.catalina.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* protocolHeader="x-forwarded-proto"
* /&gt;</code>
* <table border="1">
* <caption>Request Values</caption>
* <tr>
* <th>property</th>
* <th>Value Before RemoteIpValve</th>
* <th>Value After RemoteIpValve</th>
* </tr>
* <tr>
* <td>request.remoteAddr</td>
* <td>192.168.0.10</td>
* <td>140.211.11.130</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-for']</td>
* <td>140.211.11.130, 192.168.0.10</td>
* <td>null</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-by']</td>
* <td>null</td>
* <td>null</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-proto']</td>
* <td>https</td>
* <td>https</td>
* </tr>
* <tr>
* <td>request.scheme</td>
* <td>http</td>
* <td>https</td>
* </tr>
* <tr>
* <td>request.secure</td>
* <td>false</td>
* <td>true</td>
* </tr>
* <tr>
* <td>request.serverPort</td>
* <td>80</td>
* <td>443</td>
* </tr>
* </table>
* <p>
* Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed by the request.
* <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
* </p>
* <hr>
* <p>
* <strong>Sample with trusted proxies</strong>
* </p>
* <p>
* RemoteIpValve configuration:
* </p>
* <code>
* &lt;Valve
* className="org.apache.catalina.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* trustedProxies="proxy1|proxy2"
* /&gt;</code>
* <table border="1">
* <caption>Request Values</caption>
* <tr>
* <th>property</th>
* <th>Value Before RemoteIpValve</th>
* <th>Value After RemoteIpValve</th>
* </tr>
* <tr>
* <td>request.remoteAddr</td>
* <td>192.168.0.10</td>
* <td>140.211.11.130</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-for']</td>
* <td>140.211.11.130, proxy1, proxy2</td>
* <td>null</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-by']</td>
* <td>null</td>
* <td>proxy1, proxy2</td>
* </tr>
* </table>
* <p>
* Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
* are migrated in <code>x-forwarded-by</code> header. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
* </p>
* <hr>
* <p>
* <strong>Sample with internal and trusted proxies</strong>
* </p>
* <p>
* RemoteIpValve configuration:
* </p>
* <code>
* &lt;Valve
* className="org.apache.catalina.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* trustedProxies="proxy1|proxy2"
* /&gt;</code>
* <table border="1">
* <caption>Request Values</caption>
* <tr>
* <th>property</th>
* <th>Value Before RemoteIpValve</th>
* <th>Value After RemoteIpValve</th>
* </tr>
* <tr>
* <td>request.remoteAddr</td>
* <td>192.168.0.10</td>
* <td>140.211.11.130</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-for']</td>
* <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td>
* <td>null</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-by']</td>
* <td>null</td>
* <td>proxy1, proxy2</td>
* </tr>
* </table>
* <p>
* Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
* are migrated in <code>x-forwarded-by</code> header. As <code>192.168.0.10</code> is an internal proxy, it does not appear in
* <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
* </p>
* <hr>
* <p>
* <strong>Sample with an untrusted proxy</strong>
* </p>
* <p>
* RemoteIpValve configuration:
* </p>
* <code>
* &lt;Valve
* className="org.apache.catalina.valves.RemoteIpValve"
* internalProxies="192\.168\.0\.10|192\.168\.0\.11"
* remoteIpHeader="x-forwarded-for"
* proxiesHeader="x-forwarded-by"
* trustedProxies="proxy1|proxy2"
* /&gt;</code>
* <table border="1">
* <caption>Request Values</caption>
* <tr>
* <th>property</th>
* <th>Value Before RemoteIpValve</th>
* <th>Value After RemoteIpValve</th>
* </tr>
* <tr>
* <td>request.remoteAddr</td>
* <td>192.168.0.10</td>
* <td>untrusted-proxy</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-for']</td>
* <td>140.211.11.130, untrusted-proxy, proxy1</td>
* <td>140.211.11.130</td>
* </tr>
* <tr>
* <td>request.header['x-forwarded-by']</td>
* <td>null</td>
* <td>proxy1</td>
* </tr>
* </table>
* <p>
* Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>. <code>x-forwarded-by</code> holds
* <code>140.211.11.130</code> because <code>untrusted-proxy</code> is not trusted and thus, we cannot trust that
* <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is <code>untrusted-proxy</code> that is an IP
* verified by <code>proxy1</code>.
* </p>
*/
public class RemoteIpValve extends ValveBase {
/**
* {@link Pattern} for a comma delimited string that support whitespace characters
*/
private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*");
/**
* Logger
*/
private static final Log log = LogFactory.getLog(RemoteIpValve.class);
/**
* Convert a given comma delimited String into an array of String
* @param commaDelimitedStrings The string to convert
* @return array of String (non <code>null</code>)
*/
protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) {
return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern
.split(commaDelimitedStrings);
}
/**
* Convert an array of strings in a comma delimited string
* @param stringList The string list to convert
* @return The concatenated string
*/
protected static String listToCommaDelimitedString(List<String> stringList) {
if (stringList == null) {
return "";
}
StringBuilder result = new StringBuilder();
for (Iterator<String> it = stringList.iterator(); it.hasNext();) {
Object element = it.next();
if (element != null) {
result.append(element);
if (it.hasNext()) {
result.append(", ");
}
}
}
return result.toString();
}
private String hostHeader = null;
private boolean changeLocalName = false;
/**
* @see #setHttpServerPort(int)
*/
private int httpServerPort = 80;
/**
* @see #setHttpsServerPort(int)
*/
private int httpsServerPort = 443;
private String portHeader = null;
private boolean changeLocalPort = false;
/**
* @see #setInternalProxies(String)
*/
private Pattern internalProxies = Pattern.compile(
"10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +
"192\\.168\\.\\d{1,3}\\.\\d{1,3}|" +
"169\\.254\\.\\d{1,3}\\.\\d{1,3}|" +
"127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +
"172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" +
"172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" +
"172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|" +
"0:0:0:0:0:0:0:1|::1");
/**
* @see #setProtocolHeader(String)
*/
private String protocolHeader = "X-Forwarded-Proto";
/**
* @see #setProtocolHeaderHttpsValue(String)
*/
private String protocolHeaderHttpsValue = "https";
/**
* @see #setProxiesHeader(String)
*/
private String proxiesHeader = "X-Forwarded-By";
/**
* @see #setRemoteIpHeader(String)
*/
private String remoteIpHeader = "X-Forwarded-For";
/**
* @see #setRequestAttributesEnabled(boolean)
*/
private boolean requestAttributesEnabled = true;
/**
* @see RemoteIpValve#setTrustedProxies(String)
*/
private Pattern trustedProxies = null;
/**
* Default constructor that ensures {@link ValveBase#ValveBase(boolean)} is
* called with <code>true</code>.
*/
public RemoteIpValve() {
// Async requests are supported with this valve
super(true);
}
/**
* Obtain the name of the HTTP header used to override the value returned
* by {@link Request#getServerName()} and (optionally depending on {link
* {@link #isChangeLocalName()} {@link Request#getLocalName()}.
*
* @return The HTTP header name
*/
public String getHostHeader() {
return hostHeader;
}
/**
* Set the name of the HTTP header used to override the value returned
* by {@link Request#getServerName()} and (optionally depending on {link
* {@link #isChangeLocalName()} {@link Request#getLocalName()}.
*
* @param hostHeader The HTTP header name
*/
public void setHostHeader(String hostHeader) {
this.hostHeader = hostHeader;
}
public boolean isChangeLocalName() {
return changeLocalName;
}
public void setChangeLocalName(boolean changeLocalName) {
this.changeLocalName = changeLocalName;
}
public int getHttpServerPort() {
return httpServerPort;
}
public int getHttpsServerPort() {
return httpsServerPort;
}
/**
* Obtain the name of the HTTP header used to override the value returned
* by {@link Request#getServerPort()} and (optionally depending on {link
* {@link #isChangeLocalPort()} {@link Request#getLocalPort()}.
*
* @return The HTTP header name
*/
public String getPortHeader() {
return portHeader;
}
/**
* Set the name of the HTTP header used to override the value returned
* by {@link Request#getServerPort()} and (optionally depending on {link
* {@link #isChangeLocalPort()} {@link Request#getLocalPort()}.
*
* @param portHeader The HTTP header name
*/
public void setPortHeader(String portHeader) {
this.portHeader = portHeader;
}
public boolean isChangeLocalPort() {
return changeLocalPort;
}
public void setChangeLocalPort(boolean changeLocalPort) {
this.changeLocalPort = changeLocalPort;
}
/**
* @see #setInternalProxies(String)
* @return Regular expression that defines the internal proxies
*/
public String getInternalProxies() {
if (internalProxies == null) {
return null;
}
return internalProxies.toString();
}
/**
* @see #setProtocolHeader(String)
* @return the protocol header (e.g. "X-Forwarded-Proto")
*/
public String getProtocolHeader() {
return protocolHeader;
}
/**
* @see RemoteIpValve#setProtocolHeaderHttpsValue(String)
* @return the value of the protocol header for incoming https request (e.g. "https")
*/
public String getProtocolHeaderHttpsValue() {
return protocolHeaderHttpsValue;
}
/**
* @see #setProxiesHeader(String)
* @return the proxies header name (e.g. "X-Forwarded-By")
*/
public String getProxiesHeader() {
return proxiesHeader;
}
/**
* @see #setRemoteIpHeader(String)
* @return the remote IP header name (e.g. "X-Forwarded-For")
*/
public String getRemoteIpHeader() {
return remoteIpHeader;
}
/**
* @see #setRequestAttributesEnabled(boolean)
* @return <code>true</code> if the attributes will be logged, otherwise
* <code>false</code>
*/
public boolean getRequestAttributesEnabled() {
return requestAttributesEnabled;
}
/**
* @see #setTrustedProxies(String)
* @return Regular expression that defines the trusted proxies
*/
public String getTrustedProxies() {
if (trustedProxies == null) {
return null;
}
return trustedProxies.toString();
}
/**
* {@inheritDoc}
*/
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
final String originalRemoteAddr = request.getRemoteAddr();
final String originalRemoteHost = request.getRemoteHost();
final String originalScheme = request.getScheme();
final boolean originalSecure = request.isSecure();
final String originalServerName = request.getServerName();
final String originalLocalName = request.getLocalName();
final int originalServerPort = request.getServerPort();
final int originalLocalPort = request.getLocalPort();
final String originalProxiesHeader = request.getHeader(proxiesHeader);
final String originalRemoteIpHeader = request.getHeader(remoteIpHeader);
boolean isInternal = internalProxies != null &&
internalProxies.matcher(originalRemoteAddr).matches();
if (isInternal || (trustedProxies != null &&
trustedProxies.matcher(originalRemoteAddr).matches())) {
String remoteIp = null;
// In java 6, proxiesHeaderValue should be declared as a java.util.Deque
LinkedList<String> proxiesHeaderValue = new LinkedList<>();
StringBuilder concatRemoteIpHeaderValue = new StringBuilder();
for (Enumeration<String> e = request.getHeaders(remoteIpHeader); e.hasMoreElements();) {
if (concatRemoteIpHeaderValue.length() > 0) {
concatRemoteIpHeaderValue.append(", ");
}
concatRemoteIpHeaderValue.append(e.nextElement());
}
String[] remoteIpHeaderValue = commaDelimitedListToStringArray(concatRemoteIpHeaderValue.toString());
int idx;
if (!isInternal) {
proxiesHeaderValue.addFirst(originalRemoteAddr);
}
// loop on remoteIpHeaderValue to find the first trusted remote ip and to build the proxies chain
for (idx = remoteIpHeaderValue.length - 1; idx >= 0; idx--) {
String currentRemoteIp = remoteIpHeaderValue[idx];
remoteIp = currentRemoteIp;
if (internalProxies !=null && internalProxies.matcher(currentRemoteIp).matches()) {
// do nothing, internalProxies IPs are not appended to the
} else if (trustedProxies != null &&
trustedProxies.matcher(currentRemoteIp).matches()) {
proxiesHeaderValue.addFirst(currentRemoteIp);
} else {
idx--; // decrement idx because break statement doesn't do it
break;
}
}
// continue to loop on remoteIpHeaderValue to build the new value of the remoteIpHeader
LinkedList<String> newRemoteIpHeaderValue = new LinkedList<>();
for (; idx >= 0; idx--) {
String currentRemoteIp = remoteIpHeaderValue[idx];
newRemoteIpHeaderValue.addFirst(currentRemoteIp);
}
if (remoteIp != null) {
request.setRemoteAddr(remoteIp);
request.setRemoteHost(remoteIp);
if (proxiesHeaderValue.size() == 0) {
request.getCoyoteRequest().getMimeHeaders().removeHeader(proxiesHeader);
} else {
String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue);
request.getCoyoteRequest().getMimeHeaders().setValue(proxiesHeader).setString(commaDelimitedListOfProxies);
}
if (newRemoteIpHeaderValue.size() == 0) {
request.getCoyoteRequest().getMimeHeaders().removeHeader(remoteIpHeader);
} else {
String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue);
request.getCoyoteRequest().getMimeHeaders().setValue(remoteIpHeader).setString(commaDelimitedRemoteIpHeaderValue);
}
}
if (protocolHeader != null) {
String protocolHeaderValue = request.getHeader(protocolHeader);
if (protocolHeaderValue == null) {
// Don't modify the secure, scheme and serverPort attributes
// of the request
} else if (isForwardedProtoHeaderValueSecure(protocolHeaderValue)) {
request.setSecure(true);
request.getCoyoteRequest().scheme().setString("https");
setPorts(request, httpsServerPort);
} else {
request.setSecure(false);
request.getCoyoteRequest().scheme().setString("http");
setPorts(request, httpServerPort);
}
}
if (hostHeader != null) {
String hostHeaderValue = request.getHeader(hostHeader);
if (hostHeaderValue != null) {
try {
int portIndex = Host.parse(hostHeaderValue);
if (portIndex > -1) {
log.debug(sm.getString("remoteIpValve.invalidHostWithPort", hostHeaderValue, hostHeader));
hostHeaderValue = hostHeaderValue.substring(0, portIndex);
}
request.getCoyoteRequest().serverName().setString(hostHeaderValue);
if (isChangeLocalName()) {
request.getCoyoteRequest().localName().setString(hostHeaderValue);
}
} catch (IllegalArgumentException iae) {
log.debug(sm.getString("remoteIpValve.invalidHostHeader", hostHeaderValue, hostHeader));
}
}
}
request.setAttribute(Globals.REQUEST_FORWARDED_ATTRIBUTE, Boolean.TRUE);
if (log.isDebugEnabled()) {
log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr [" + originalRemoteAddr +
"], originalRemoteHost=[" + originalRemoteHost + "], originalSecure=[" + originalSecure +
"], originalScheme=[" + originalScheme + "], originalServerName=[" + originalServerName +
"], originalServerPort=[" + originalServerPort +
"] will be seen as newRemoteAddr=[" + request.getRemoteAddr() +
"], newRemoteHost=[" + request.getRemoteHost() + "], newSecure=[" + request.isSecure() +
"], newScheme=[" + request.getScheme() + "], newServerName=[" + request.getServerName() +
"], newServerPort=[" + request.getServerPort() + "]");
}
} else {
if (log.isDebugEnabled()) {
log.debug("Skip RemoteIpValve for request " + request.getRequestURI() + " with originalRemoteAddr '"
+ request.getRemoteAddr() + "'");
}
}
if (requestAttributesEnabled) {
request.setAttribute(AccessLog.REMOTE_ADDR_ATTRIBUTE,
request.getRemoteAddr());
request.setAttribute(Globals.REMOTE_ADDR_ATTRIBUTE,
request.getRemoteAddr());
request.setAttribute(AccessLog.REMOTE_HOST_ATTRIBUTE,
request.getRemoteHost());
request.setAttribute(AccessLog.PROTOCOL_ATTRIBUTE,
request.getProtocol());
request.setAttribute(AccessLog.SERVER_NAME_ATTRIBUTE,
request.getServerName());
request.setAttribute(AccessLog.SERVER_PORT_ATTRIBUTE,
Integer.valueOf(request.getServerPort()));
}
try {
getNext().invoke(request, response);
} finally {
request.setRemoteAddr(originalRemoteAddr);
request.setRemoteHost(originalRemoteHost);
request.setSecure(originalSecure);
request.getCoyoteRequest().scheme().setString(originalScheme);
request.getCoyoteRequest().serverName().setString(originalServerName);
request.getCoyoteRequest().localName().setString(originalLocalName);
request.setServerPort(originalServerPort);
request.setLocalPort(originalLocalPort);
MimeHeaders headers = request.getCoyoteRequest().getMimeHeaders();
if (originalProxiesHeader == null || originalProxiesHeader.length() == 0) {
headers.removeHeader(proxiesHeader);
} else {
headers.setValue(proxiesHeader).setString(originalProxiesHeader);
}
if (originalRemoteIpHeader == null || originalRemoteIpHeader.length() == 0) {
headers.removeHeader(remoteIpHeader);
} else {
headers.setValue(remoteIpHeader).setString(originalRemoteIpHeader);
}
}
}
/*
* Considers the value to be secure if it exclusively holds forwards for
* {@link #protocolHeaderHttpsValue}.
*/
private boolean isForwardedProtoHeaderValueSecure(String protocolHeaderValue) {
if (!protocolHeaderValue.contains(",")) {
return protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue);
}
String[] forwardedProtocols = commaDelimitedListToStringArray(protocolHeaderValue);
if (forwardedProtocols.length == 0) {
return false;
}
for (int i = 0; i < forwardedProtocols.length; i++) {
if (!protocolHeaderHttpsValue.equalsIgnoreCase(forwardedProtocols[i])) {
return false;
}
}
return true;
}
private void setPorts(Request request, int defaultPort) {
int port = defaultPort;
if (portHeader != null) {
String portHeaderValue = request.getHeader(portHeader);
if (portHeaderValue != null) {
try {
port = Integer.parseInt(portHeaderValue);
} catch (NumberFormatException nfe) {
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"remoteIpValve.invalidPortHeader",
portHeaderValue, portHeader), nfe);
}
}
}
}
request.setServerPort(port);
if (changeLocalPort) {
request.setLocalPort(port);
}
}
/**
* <p>
* Server Port value if the {@link #protocolHeader} is not <code>null</code> and does not indicate HTTP
* </p>
* <p>
* Default value : 80
* </p>
* @param httpServerPort The server port
*/
public void setHttpServerPort(int httpServerPort) {
this.httpServerPort = httpServerPort;
}
/**
* <p>
* Server Port value if the {@link #protocolHeader} indicates HTTPS
* </p>
* <p>
* Default value : 443
* </p>
* @param httpsServerPort The server port
*/
public void setHttpsServerPort(int httpsServerPort) {
this.httpsServerPort = httpsServerPort;
}
/**
* <p>
* Regular expression that defines the internal proxies.
* </p>
* <p>
* Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|169\.254.\d{1,3}.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|0:0:0:0:0:0:0:1
* </p>
* @param internalProxies The proxy regular expression
*/
public void setInternalProxies(String internalProxies) {
if (internalProxies == null || internalProxies.length() == 0) {
this.internalProxies = null;
} else {
this.internalProxies = Pattern.compile(internalProxies);
}
}
/**
* <p>
* Header that holds the incoming protocol, usually named <code>X-Forwarded-Proto</code>. If <code>null</code>, request.scheme and
* request.secure will not be modified.
* </p>
* <p>
* Default value : <code>null</code>
* </p>
* @param protocolHeader The header name
*/
public void setProtocolHeader(String protocolHeader) {
this.protocolHeader = protocolHeader;
}
/**
* <p>
* Case insensitive value of the protocol header to indicate that the incoming http request uses SSL.
* </p>
* <p>
* Default value : <code>https</code>
* </p>
* @param protocolHeaderHttpsValue The header name
*/
public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) {
this.protocolHeaderHttpsValue = protocolHeaderHttpsValue;
}
/**
* <p>
* The proxiesHeader directive specifies a header into which mod_remoteip will collect a list of all of the intermediate client IP
* addresses trusted to resolve the actual remote IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header,
* while any intermediate RemoteIPInternalProxy addresses are discarded.
* </p>
* <p>
* Name of the http header that holds the list of trusted proxies that has been traversed by the http request.
* </p>
* <p>
* The value of this header can be comma delimited.
* </p>
* <p>
* Default value : <code>X-Forwarded-By</code>
* </p>
* @param proxiesHeader The header name
*/
public void setProxiesHeader(String proxiesHeader) {
this.proxiesHeader = proxiesHeader;
}
/**
* <p>
* Name of the http header from which the remote ip is extracted.
* </p>
* <p>
* The value of this header can be comma delimited.
* </p>
* <p>
* Default value : <code>X-Forwarded-For</code>
* </p>
*
* @param remoteIpHeader The header name
*/
public void setRemoteIpHeader(String remoteIpHeader) {
this.remoteIpHeader = remoteIpHeader;
}
/**
* Should this valve set request attributes for IP address, Hostname,
* protocol and port used for the request? This are typically used in
* conjunction with the {@link AccessLog} which will otherwise log the
* original values. Default is <code>true</code>.
*
* The attributes set are:
* <ul>
* <li>org.apache.catalina.AccessLog.RemoteAddr</li>
* <li>org.apache.catalina.AccessLog.RemoteHost</li>
* <li>org.apache.catalina.AccessLog.Protocol</li>
* <li>org.apache.catalina.AccessLog.ServerPort</li>
* <li>org.apache.tomcat.remoteAddr</li>
* </ul>
*
* @param requestAttributesEnabled <code>true</code> causes the attributes
* to be set, <code>false</code> disables
* the setting of the attributes.
*/
public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
this.requestAttributesEnabled = requestAttributesEnabled;
}
/**
* <p>
* Regular expression defining proxies that are trusted when they appear in
* the {@link #remoteIpHeader} header.
* </p>
* <p>
* Default value : empty list, no external proxy is trusted.
* </p>
* @param trustedProxies The regular expression
*/
public void setTrustedProxies(String trustedProxies) {
if (trustedProxies == null || trustedProxies.length() == 0) {
this.trustedProxies = null;
} else {
this.trustedProxies = Pattern.compile(trustedProxies);
}
}
}

View File

@@ -0,0 +1,424 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.juli.logging.Log;
/**
* Implementation of a Valve that performs filtering based on comparing the
* appropriate request property (selected based on which subclass you choose
* to configure into your Container's pipeline) against the regular expressions
* configured for this Valve.
* <p>
* This valve is configured by setting the <code>allow</code> and/or
* <code>deny</code> properties to a regular expressions (in the syntax
* supported by {@link Pattern}) to which the appropriate request property will
* be compared. Evaluation proceeds as follows:
* <ul>
* <li>The subclass extracts the request property to be filtered, and
* calls the common <code>process()</code> method.
* <li>If there is a deny expression configured, the property will be compared
* to the expression. If a match is found, this request will be rejected
* with a "Forbidden" HTTP response.</li>
* <li>If there is a allow expression configured, the property will be compared
* to each such expression. If a match is found, this request will be
* allowed to pass through to the next Valve in the current pipeline.</li>
* <li>If a deny expression was specified but no allow expression, allow this
* request to pass through (because none of the deny expressions matched
* it).
* <li>The request will be rejected with a "Forbidden" HTTP response.</li>
* </ul>
* <p>
* As an option the valve can generate an invalid <code>authenticate</code>
* header instead of denying the request. This can be combined with the
* context attribute <code>preemptiveAuthentication="true"</code> and an
* authenticator to force authentication instead of denial.
* <p>
* This Valve may be attached to any Container, depending on the granularity
* of the filtering you wish to perform.
*
* @author Craig R. McClanahan
*/
public abstract class RequestFilterValve extends ValveBase {
//------------------------------------------------------ Constructor
public RequestFilterValve() {
super(true);
}
// ----------------------------------------------------- Instance Variables
/**
* The regular expression used to test for allowed requests.
*/
protected volatile Pattern allow = null;
/**
* The current allow configuration value that may or may not compile into a
* valid {@link Pattern}.
*/
protected volatile String allowValue = null;
/**
* Helper variable to catch configuration errors.
* It is <code>true</code> by default, but becomes <code>false</code>
* if there was an attempt to assign an invalid value to the
* <code>allow</code> pattern.
*/
protected volatile boolean allowValid = true;
/**
* The regular expression used to test for denied requests.
*/
protected volatile Pattern deny = null;
/**
* The current deny configuration value that may or may not compile into a
* valid {@link Pattern}.
*/
protected volatile String denyValue = null;
/**
* Helper variable to catch configuration errors.
* It is <code>true</code> by default, but becomes <code>false</code>
* if there was an attempt to assign an invalid value to the
* <code>deny</code> pattern.
*/
protected volatile boolean denyValid = true;
/**
* The HTTP response status code that is used when rejecting denied
* request. It is 403 by default, but may be changed to be 404.
*/
protected int denyStatus = HttpServletResponse.SC_FORBIDDEN;
/**
* <p>If <code>invalidAuthenticationWhenDeny</code> is true
* and the context has <code>preemptiveAuthentication</code>
* set, set an invalid authorization header to trigger basic auth
* instead of denying the request..
*/
private boolean invalidAuthenticationWhenDeny = false;
/**
* Flag deciding whether we add the server connector port to the property
* compared in the filtering method. The port will be appended
* using a ";" as a separator.
*/
private volatile boolean addConnectorPort = false;
// ------------------------------------------------------------- Properties
/**
* Return the regular expression used to test for allowed requests for this
* Valve, if any; otherwise, return <code>null</code>.
* @return the regular expression
*/
public String getAllow() {
return allowValue;
}
/**
* Set the regular expression used to test for allowed requests for this
* Valve, if any.
*
* @param allow The new allow expression
*/
public void setAllow(String allow) {
if (allow == null || allow.length() == 0) {
this.allow = null;
allowValue = null;
allowValid = true;
} else {
boolean success = false;
try {
allowValue = allow;
this.allow = Pattern.compile(allow);
success = true;
} finally {
allowValid = success;
}
}
}
/**
* Return the regular expression used to test for denied requests for this
* Valve, if any; otherwise, return <code>null</code>.
* @return the regular expression
*/
public String getDeny() {
return denyValue;
}
/**
* Set the regular expression used to test for denied requests for this
* Valve, if any.
*
* @param deny The new deny expression
*/
public void setDeny(String deny) {
if (deny == null || deny.length() == 0) {
this.deny = null;
denyValue = null;
denyValid = true;
} else {
boolean success = false;
try {
denyValue = deny;
this.deny = Pattern.compile(deny);
success = true;
} finally {
denyValid = success;
}
}
}
/**
* Returns {@code false} if the last change to the {@code allow} pattern did
* not apply successfully. E.g. if the pattern is syntactically
* invalid.
* @return <code>false</code> if the current pattern is invalid
*/
public final boolean isAllowValid() {
return allowValid;
}
/**
* Returns {@code false} if the last change to the {@code deny} pattern did
* not apply successfully. E.g. if the pattern is syntactically
* invalid.
* @return <code>false</code> if the current pattern is invalid
*/
public final boolean isDenyValid() {
return denyValid;
}
/**
* @return response status code that is used to reject denied request.
*/
public int getDenyStatus() {
return denyStatus;
}
/**
* Set response status code that is used to reject denied request.
* @param denyStatus The status code
*/
public void setDenyStatus(int denyStatus) {
this.denyStatus = denyStatus;
}
/**
* @return <code>true</code> if a deny is handled by setting an invalid auth header.
*/
public boolean getInvalidAuthenticationWhenDeny() {
return invalidAuthenticationWhenDeny;
}
/**
* Set invalidAuthenticationWhenDeny property.
* @param value <code>true</code> to handle a deny by setting an invalid auth header
*/
public void setInvalidAuthenticationWhenDeny(boolean value) {
invalidAuthenticationWhenDeny = value;
}
/**
* Get the flag deciding whether we add the server connector port to the
* property compared in the filtering method. The port will be appended
* using a ";" as a separator.
* @return <code>true</code> to add the connector port
*/
public boolean getAddConnectorPort() {
return addConnectorPort;
}
/**
* Set the flag deciding whether we add the server connector port to the
* property compared in the filtering method. The port will be appended
* using a ";" as a separator.
*
* @param addConnectorPort The new flag
*/
public void setAddConnectorPort(boolean addConnectorPort) {
this.addConnectorPort = addConnectorPort;
}
// --------------------------------------------------------- Public Methods
/**
* Extract the desired request property, and pass it (along with the
* specified request and response objects) to the protected
* <code>process()</code> method to perform the actual filtering.
* This method must be implemented by a concrete subclass.
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
@Override
public abstract void invoke(Request request, Response response)
throws IOException, ServletException;
// ------------------------------------------------------ Protected Methods
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (!allowValid || !denyValid) {
throw new LifecycleException(
sm.getString("requestFilterValve.configInvalid"));
}
}
@Override
protected synchronized void startInternal() throws LifecycleException {
if (!allowValid || !denyValid) {
throw new LifecycleException(
sm.getString("requestFilterValve.configInvalid"));
}
super.startInternal();
}
/**
* Perform the filtering that has been configured for this Valve, matching
* against the specified request property.
*
* @param property The request property on which to filter
* @param request The servlet request to be processed
* @param response The servlet response to be processed
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
protected void process(String property, Request request, Response response)
throws IOException, ServletException {
if (isAllowed(property)) {
getNext().invoke(request, response);
return;
}
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("requestFilterValve.deny",
request.getRequestURI(), property));
}
// Deny this request
denyRequest(request, response);
}
protected abstract Log getLog();
/**
* Reject the request that was denied by this valve.
* <p>If <code>invalidAuthenticationWhenDeny</code> is true
* and the context has <code>preemptiveAuthentication</code>
* set, set an invalid authorization header to trigger basic auth.
*
* @param request The servlet request to be processed
* @param response The servlet response to be processed
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
protected void denyRequest(Request request, Response response)
throws IOException, ServletException {
if (invalidAuthenticationWhenDeny) {
Context context = request.getContext();
if (context != null && context.getPreemptiveAuthentication()) {
if (request.getCoyoteRequest().getMimeHeaders().getValue("authorization") == null) {
request.getCoyoteRequest().getMimeHeaders().addValue("authorization").setString("invalid");
}
getNext().invoke(request, response);
return;
}
}
response.sendError(denyStatus);
}
/**
* Perform the test implemented by this Valve, matching against the
* specified request property value. This method is public so that it can be
* called through JMX, e.g. to test whether certain IP address is allowed or
* denied by the valve configuration.
*
* @param property The request property value on which to filter
* @return <code>true</code> if the request is allowed
*/
public boolean isAllowed(String property) {
// Use local copies for thread safety
Pattern deny = this.deny;
Pattern allow = this.allow;
// Check the deny patterns, if any
if (deny != null && deny.matcher(property).matches()) {
return false;
}
// Check the allow patterns, if any
if (allow != null && allow.matcher(property).matches()) {
return true;
}
// Allow if denies specified but not allows
if (deny != null && allow == null) {
return true;
}
// Deny this request
return false;
}
}

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.catalina.valves;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.servlet.ServletException;
import org.apache.catalina.Globals;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* When using mod_proxy_http, the client SSL information is not included in the
* protocol (unlike mod_jk and mod_proxy_ajp). To make the client SSL
* information available to Tomcat, some additional configuration is required.
* In httpd, mod_headers is used to add the SSL information as HTTP headers. In
* Tomcat, this valve is used to read the information from the HTTP headers and
* insert it into the request.<p>
*
* <b>Note: Ensure that the headers are always set by httpd for all requests to
* prevent a client spoofing SSL information by sending fake headers. </b><p>
*
* In httpd.conf add the following:
* <pre>
* &lt;IfModule ssl_module&gt;
* RequestHeader set SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s"
* RequestHeader set SSL_CIPHER "%{SSL_CIPHER}s"
* RequestHeader set SSL_SESSION_ID "%{SSL_SESSION_ID}s"
* RequestHeader set SSL_CIPHER_USEKEYSIZE "%{SSL_CIPHER_USEKEYSIZE}s"
* &lt;/IfModule&gt;
* </pre>
*
* In server.xml, configure this valve under the Engine element in server.xml:
* <pre>
* &lt;Engine ...&gt;
* &lt;Valve className="org.apache.catalina.valves.SSLValve" /&gt;
* &lt;Host ... /&gt;
* &lt;/Engine&gt;
* </pre>
*/
public class SSLValve extends ValveBase {
private static final Log log = LogFactory.getLog(SSLValve.class);
private String sslClientCertHeader = "ssl_client_cert";
private String sslCipherHeader = "ssl_cipher";
private String sslSessionIdHeader = "ssl_session_id";
private String sslCipherUserKeySizeHeader = "ssl_cipher_usekeysize";
//------------------------------------------------------ Constructor
public SSLValve() {
super(true);
}
public String getSslClientCertHeader() {
return sslClientCertHeader;
}
public void setSslClientCertHeader(String sslClientCertHeader) {
this.sslClientCertHeader = sslClientCertHeader;
}
public String getSslCipherHeader() {
return sslCipherHeader;
}
public void setSslCipherHeader(String sslCipherHeader) {
this.sslCipherHeader = sslCipherHeader;
}
public String getSslSessionIdHeader() {
return sslSessionIdHeader;
}
public void setSslSessionIdHeader(String sslSessionIdHeader) {
this.sslSessionIdHeader = sslSessionIdHeader;
}
public String getSslCipherUserKeySizeHeader() {
return sslCipherUserKeySizeHeader;
}
public void setSslCipherUserKeySizeHeader(String sslCipherUserKeySizeHeader) {
this.sslCipherUserKeySizeHeader = sslCipherUserKeySizeHeader;
}
public String mygetHeader(Request request, String header) {
String strcert0 = request.getHeader(header);
if (strcert0 == null) {
return null;
}
/* mod_header writes "(null)" when the ssl variable is no filled */
if ("(null)".equals(strcert0)) {
return null;
}
return strcert0;
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
/*
* Known behaviours of reverse proxies that are handled by the
* processing below:
* - mod_header converts the '\n' into ' '
* - nginx converts the '\n' into multiple ' '
*
* The code assumes that the trimmed header value starts with
* '-----BEGIN CERTIFICATE-----' and ends with
* '-----END CERTIFICATE-----'.
*
* Note: For Java 7, the the BEGIN and END markers must be on separate
* lines as must each of the original content lines. The
* CertificateFactory is tolerant of any additional whitespace
* such as leading and trailing spaces and new lines as long as
* they do not appear in the middle of an original content line.
*/
String headerValue = mygetHeader(request, sslClientCertHeader);
if (headerValue != null) {
headerValue = headerValue.trim();
if (headerValue.length() > 27) {
String body = headerValue.substring(27, headerValue .length() - 25);
body = body.replace(' ', '\n');
body = body.replace('\t', '\n');
String header = "-----BEGIN CERTIFICATE-----\n";
String footer = "\n-----END CERTIFICATE-----\n";
String strcerts = header.concat(body).concat(footer);
ByteArrayInputStream bais = new ByteArrayInputStream(
strcerts.getBytes(StandardCharsets.ISO_8859_1));
X509Certificate jsseCerts[] = null;
String providerName = (String) request.getConnector().getProperty(
"clientCertProvider");
try {
CertificateFactory cf;
if (providerName == null) {
cf = CertificateFactory.getInstance("X.509");
} else {
cf = CertificateFactory.getInstance("X.509", providerName);
}
X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
jsseCerts = new X509Certificate[1];
jsseCerts[0] = cert;
} catch (java.security.cert.CertificateException e) {
log.warn(sm.getString("sslValve.certError", strcerts), e);
} catch (NoSuchProviderException e) {
log.error(sm.getString(
"sslValve.invalidProvider", providerName), e);
}
request.setAttribute(Globals.CERTIFICATES_ATTR, jsseCerts);
}
}
headerValue = mygetHeader(request, sslCipherHeader);
if (headerValue != null) {
request.setAttribute(Globals.CIPHER_SUITE_ATTR, headerValue);
}
headerValue = mygetHeader(request, sslSessionIdHeader);
if (headerValue != null) {
request.setAttribute(Globals.SSL_SESSION_ID_ATTR, headerValue);
}
headerValue = mygetHeader(request, sslCipherUserKeySizeHeader);
if (headerValue != null) {
request.setAttribute(Globals.KEY_SIZE_ATTR, Integer.valueOf(headerValue));
}
getNext().invoke(request, response);
}
}

View File

@@ -0,0 +1,199 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.util.concurrent.Semaphore;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
/**
* <p>Implementation of a Valve that limits concurrency.</p>
*
* <p>This Valve may be attached to any Container, depending on the granularity
* of the concurrency control you wish to perform. Note that internally, some
* async requests may require multiple serial requests to complete what - to the
* user - appears as a single request.</p>
*
* @author Remy Maucherat
*/
public class SemaphoreValve extends ValveBase {
//------------------------------------------------------ Constructor
public SemaphoreValve() {
super(true);
}
// ----------------------------------------------------- Instance Variables
/**
* Semaphore.
*/
protected Semaphore semaphore = null;
// ------------------------------------------------------------- Properties
/**
* Concurrency level of the semaphore.
*/
protected int concurrency = 10;
public int getConcurrency() { return concurrency; }
public void setConcurrency(int concurrency) { this.concurrency = concurrency; }
/**
* Fairness of the semaphore.
*/
protected boolean fairness = false;
public boolean getFairness() { return fairness; }
public void setFairness(boolean fairness) { this.fairness = fairness; }
/**
* Block until a permit is available.
*/
protected boolean block = true;
public boolean getBlock() { return block; }
public void setBlock(boolean block) { this.block = block; }
/**
* Block interruptibly until a permit is available.
*/
protected boolean interruptible = false;
public boolean getInterruptible() { return interruptible; }
public void setInterruptible(boolean interruptible) { this.interruptible = interruptible; }
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
semaphore = new Semaphore(concurrency, fairness);
setState(LifecycleState.STARTING);
}
/**
* Stop this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
semaphore = null;
}
// --------------------------------------------------------- Public Methods
/**
* Do concurrency control on the request using the semaphore.
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
if (controlConcurrency(request, response)) {
boolean shouldRelease = true;
try {
if (block) {
if (interruptible) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
shouldRelease = false;
permitDenied(request, response);
return;
}
} else {
semaphore.acquireUninterruptibly();
}
} else {
if (!semaphore.tryAcquire()) {
shouldRelease = false;
permitDenied(request, response);
return;
}
}
getNext().invoke(request, response);
} finally {
if (shouldRelease) {
semaphore.release();
}
}
} else {
getNext().invoke(request, response);
}
}
/**
* Subclass friendly method to add conditions.
* @param request The Servlet request
* @param response The Servlet response
* @return <code>true</code> if the concurrency control should occur
* on this request
*/
public boolean controlConcurrency(Request request, Response response) {
return true;
}
/**
* Subclass friendly method to add error handling when a permit isn't
* granted.
* @param request The Servlet request
* @param response The Servlet response
* @throws IOException Error writing output
* @throws ServletException Other error
*/
public void permitDenied(Request request, Response response)
throws IOException, ServletException {
// NO-OP by default
}
}

View File

@@ -0,0 +1,425 @@
/*
* 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.catalina.valves;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
/**
* This valve allows to detect requests that take a long time to process, which
* might indicate that the thread that is processing it is stuck.
*/
public class StuckThreadDetectionValve extends ValveBase {
/**
* Logger
*/
private static final Log log = LogFactory.getLog(StuckThreadDetectionValve.class);
/**
* The string manager for this package.
*/
private static final StringManager sm =
StringManager.getManager(Constants.Package);
/**
* Keeps count of the number of stuck threads detected
*/
private final AtomicInteger stuckCount = new AtomicInteger(0);
/**
* Keeps count of the number of stuck threads that have been interrupted
*/
private AtomicLong interruptedThreadsCount = new AtomicLong();
/**
* In seconds. Default 600 (10 minutes).
*/
private int threshold = 600;
/**
* In seconds. Default is -1 to disable interruption.
*/
private int interruptThreadThreshold;
/**
* The only references we keep to actual running Thread objects are in
* this Map (which is automatically cleaned in invoke()s finally clause).
* That way, Threads can be GC'ed, even though the Valve still thinks they
* are stuck (caused by a long monitor interval)
*/
private final Map<Long, MonitoredThread> activeThreads = new ConcurrentHashMap<>();
private final Queue<CompletedStuckThread> completedStuckThreadsQueue =
new ConcurrentLinkedQueue<>();
/**
* Specifies the threshold (in seconds) used when checking for stuck threads.
* If &lt;=0, the detection is disabled. The default is 600 seconds.
*
* @param threshold
* The new threshold in seconds
*/
public void setThreshold(int threshold) {
this.threshold = threshold;
}
/**
* @see #setThreshold(int)
* @return The current threshold in seconds
*/
public int getThreshold() {
return threshold;
}
public int getInterruptThreadThreshold() {
return interruptThreadThreshold;
}
/**
* Specifies the threshold (in seconds) before stuck threads are interrupted.
* If &lt;=0, the interruption is disabled. The default is -1.
* If &gt;=0, the value must actually be &gt;= threshold.
*
* @param interruptThreadThreshold
* The new thread interruption threshold in seconds
*/
public void setInterruptThreadThreshold(int interruptThreadThreshold) {
this.interruptThreadThreshold = interruptThreadThreshold;
}
/**
* Required to enable async support.
*/
public StuckThreadDetectionValve() {
super(true);
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (log.isDebugEnabled()) {
log.debug("Monitoring stuck threads with threshold = "
+ threshold
+ " sec");
}
}
private void notifyStuckThreadDetected(MonitoredThread monitoredThread,
long activeTime, int numStuckThreads) {
if (log.isWarnEnabled()) {
String msg = sm.getString(
"stuckThreadDetectionValve.notifyStuckThreadDetected",
monitoredThread.getThread().getName(),
Long.valueOf(activeTime),
monitoredThread.getStartTime(),
Integer.valueOf(numStuckThreads),
monitoredThread.getRequestUri(),
Integer.valueOf(threshold),
String.valueOf(monitoredThread.getThread().getId())
);
// msg += "\n" + getStackTraceAsString(trace);
Throwable th = new Throwable();
th.setStackTrace(monitoredThread.getThread().getStackTrace());
log.warn(msg, th);
}
}
private void notifyStuckThreadCompleted(CompletedStuckThread thread,
int numStuckThreads) {
if (log.isWarnEnabled()) {
String msg = sm.getString(
"stuckThreadDetectionValve.notifyStuckThreadCompleted",
thread.getName(),
Long.valueOf(thread.getTotalActiveTime()),
Integer.valueOf(numStuckThreads),
String.valueOf(thread.getId()));
// Since the "stuck thread notification" is warn, this should also
// be warn
log.warn(msg);
}
}
/**
* {@inheritDoc}
*/
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
if (threshold <= 0) {
// short-circuit if not monitoring stuck threads
getNext().invoke(request, response);
return;
}
// Save the thread/runnable
// Keeping a reference to the thread object here does not prevent
// GC'ing, as the reference is removed from the Map in the finally clause
Long key = Long.valueOf(Thread.currentThread().getId());
StringBuffer requestUrl = request.getRequestURL();
if(request.getQueryString()!=null) {
requestUrl.append("?");
requestUrl.append(request.getQueryString());
}
MonitoredThread monitoredThread = new MonitoredThread(Thread.currentThread(),
requestUrl.toString(), interruptThreadThreshold > 0);
activeThreads.put(key, monitoredThread);
try {
getNext().invoke(request, response);
} finally {
activeThreads.remove(key);
if (monitoredThread.markAsDone() == MonitoredThreadState.STUCK) {
if(monitoredThread.wasInterrupted()) {
interruptedThreadsCount.incrementAndGet();
}
completedStuckThreadsQueue.add(
new CompletedStuckThread(monitoredThread.getThread(),
monitoredThread.getActiveTimeInMillis()));
}
}
}
@Override
public void backgroundProcess() {
super.backgroundProcess();
long thresholdInMillis = threshold * 1000L;
// Check monitored threads, being careful that the request might have
// completed by the time we examine it
for (MonitoredThread monitoredThread : activeThreads.values()) {
long activeTime = monitoredThread.getActiveTimeInMillis();
if (activeTime >= thresholdInMillis && monitoredThread.markAsStuckIfStillRunning()) {
int numStuckThreads = stuckCount.incrementAndGet();
notifyStuckThreadDetected(monitoredThread, activeTime, numStuckThreads);
}
if(interruptThreadThreshold > 0 && activeTime >= interruptThreadThreshold*1000L) {
monitoredThread.interruptIfStuck(interruptThreadThreshold);
}
}
// Check if any threads previously reported as stuck, have finished.
for (CompletedStuckThread completedStuckThread = completedStuckThreadsQueue.poll();
completedStuckThread != null; completedStuckThread = completedStuckThreadsQueue.poll()) {
int numStuckThreads = stuckCount.decrementAndGet();
notifyStuckThreadCompleted(completedStuckThread, numStuckThreads);
}
}
public int getStuckThreadCount() {
return stuckCount.get();
}
public long[] getStuckThreadIds() {
List<Long> idList = new ArrayList<>();
for (MonitoredThread monitoredThread : activeThreads.values()) {
if (monitoredThread.isMarkedAsStuck()) {
idList.add(Long.valueOf(monitoredThread.getThread().getId()));
}
}
long[] result = new long[idList.size()];
for (int i = 0; i < result.length; i++) {
result[i] = idList.get(i).longValue();
}
return result;
}
public String[] getStuckThreadNames() {
List<String> nameList = new ArrayList<>();
for (MonitoredThread monitoredThread : activeThreads.values()) {
if (monitoredThread.isMarkedAsStuck()) {
nameList.add(monitoredThread.getThread().getName());
}
}
return nameList.toArray(new String[nameList.size()]);
}
public long getInterruptedThreadsCount() {
return interruptedThreadsCount.get();
}
private static class MonitoredThread {
/**
* Reference to the thread to get a stack trace from background task
*/
private final Thread thread;
private final String requestUri;
private final long start;
private final AtomicInteger state = new AtomicInteger(
MonitoredThreadState.RUNNING.ordinal());
/**
* Semaphore to synchronize the stuck thread with the background-process
* thread. It's not used if the interruption feature is not active.
*/
private final Semaphore interruptionSemaphore;
/**
* Set to true after the thread is interrupted. No need to make it
* volatile since it is accessed right after acquiring the semaphore.
*/
private boolean interrupted;
public MonitoredThread(Thread thread, String requestUri,
boolean interruptible) {
this.thread = thread;
this.requestUri = requestUri;
this.start = System.currentTimeMillis();
if (interruptible) {
interruptionSemaphore = new Semaphore(1);
} else {
interruptionSemaphore = null;
}
}
public Thread getThread() {
return this.thread;
}
public String getRequestUri() {
return requestUri;
}
public long getActiveTimeInMillis() {
return System.currentTimeMillis() - start;
}
public Date getStartTime() {
return new Date(start);
}
public boolean markAsStuckIfStillRunning() {
return this.state.compareAndSet(MonitoredThreadState.RUNNING.ordinal(),
MonitoredThreadState.STUCK.ordinal());
}
public MonitoredThreadState markAsDone() {
int val = this.state.getAndSet(MonitoredThreadState.DONE.ordinal());
MonitoredThreadState threadState = MonitoredThreadState.values()[val];
if (threadState == MonitoredThreadState.STUCK
&& interruptionSemaphore != null) {
try {
// use the semaphore to synchronize with the background thread
// which might try to interrupt this current thread.
// Otherwise, the current thread might be interrupted after
// going out from here, maybe already serving a new request
this.interruptionSemaphore.acquire();
} catch (InterruptedException e) {
log.debug(
"thread interrupted after the request is finished, ignoring",
e);
}
// no need to release the semaphore, it will be GCed
}
//else the request went through before being marked as stuck, no need
//to sync against the semaphore
return threadState;
}
boolean isMarkedAsStuck() {
return this.state.get() == MonitoredThreadState.STUCK.ordinal();
}
public boolean interruptIfStuck(long interruptThreadThreshold) {
if (!isMarkedAsStuck() || interruptionSemaphore == null
|| !this.interruptionSemaphore.tryAcquire()) {
// if the semaphore is already acquired, it means that the
// request thread got unstuck before we interrupted it
return false;
}
try {
if (log.isWarnEnabled()) {
String msg = sm.getString(
"stuckThreadDetectionValve.notifyStuckThreadInterrupted",
this.getThread().getName(),
Long.valueOf(getActiveTimeInMillis()),
this.getStartTime(), this.getRequestUri(),
Long.valueOf(interruptThreadThreshold),
String.valueOf(this.getThread().getId()));
Throwable th = new Throwable();
th.setStackTrace(this.getThread().getStackTrace());
log.warn(msg, th);
}
this.thread.interrupt();
} finally {
this.interrupted = true;
this.interruptionSemaphore.release();
}
return true;
}
public boolean wasInterrupted() {
return interrupted;
}
}
private static class CompletedStuckThread {
private final String threadName;
private final long threadId;
private final long totalActiveTime;
public CompletedStuckThread(Thread thread, long totalActiveTime) {
this.threadName = thread.getName();
this.threadId = thread.getId();
this.totalActiveTime = totalActiveTime;
}
public String getName() {
return this.threadName;
}
public long getId() {
return this.threadId;
}
public long getTotalActiveTime() {
return this.totalActiveTime;
}
}
private enum MonitoredThreadState {
RUNNING, STUCK, DONE;
}
}

View File

@@ -0,0 +1,256 @@
/*
* 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.catalina.valves;
import org.apache.catalina.Contained;
import org.apache.catalina.Container;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.juli.logging.Log;
import org.apache.tomcat.util.res.StringManager;
/**
* Convenience base class for implementations of the <b>Valve</b> interface.
* A subclass <strong>MUST</strong> implement an <code>invoke()</code>
* method to provide the required functionality, and <strong>MAY</strong>
* implement the <code>Lifecycle</code> interface to provide configuration
* management and lifecycle support.
*
* @author Craig R. McClanahan
*/
public abstract class ValveBase extends LifecycleMBeanBase implements Contained, Valve {
protected static final StringManager sm = StringManager.getManager(ValveBase.class);
//------------------------------------------------------ Constructor
public ValveBase() {
this(false);
}
public ValveBase(boolean asyncSupported) {
this.asyncSupported = asyncSupported;
}
//------------------------------------------------------ Instance Variables
/**
* Does this valve support Servlet 3+ async requests?
*/
protected boolean asyncSupported;
/**
* The Container whose pipeline this Valve is a component of.
*/
protected Container container = null;
/**
* Container log
*/
protected Log containerLog = null;
/**
* The next Valve in the pipeline this Valve is a component of.
*/
protected Valve next = null;
//-------------------------------------------------------------- Properties
/**
* Return the Container with which this Valve is associated, if any.
*/
@Override
public Container getContainer() {
return container;
}
/**
* Set the Container with which this Valve is associated, if any.
*
* @param container The new associated container
*/
@Override
public void setContainer(Container container) {
this.container = container;
}
@Override
public boolean isAsyncSupported() {
return asyncSupported;
}
public void setAsyncSupported(boolean asyncSupported) {
this.asyncSupported = asyncSupported;
}
/**
* Return the next Valve in this pipeline, or <code>null</code> if this
* is the last Valve in the pipeline.
*/
@Override
public Valve getNext() {
return next;
}
/**
* Set the Valve that follows this one in the pipeline it is part of.
*
* @param valve The new next valve
*/
@Override
public void setNext(Valve valve) {
this.next = valve;
}
//---------------------------------------------------------- Public Methods
/**
* Execute a periodic task, such as reloading, etc. This method will be
* invoked inside the classloading context of this container. Unexpected
* throwables will be caught and logged.
*/
@Override
public void backgroundProcess() {
// NOOP by default
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
containerLog = getContainer().getLogger();
}
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
}
/**
* Stop this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
}
/**
* Return a String rendering of this object.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(this.getClass().getName());
sb.append('[');
if (container == null) {
sb.append("Container is null");
} else {
sb.append(container.getName());
}
sb.append(']');
return sb.toString();
}
// -------------------- JMX and Registration --------------------
@Override
public String getObjectNameKeyProperties() {
StringBuilder name = new StringBuilder("type=Valve");
Container container = getContainer();
name.append(container.getMBeanKeyProperties());
int seq = 0;
// Pipeline may not be present in unit testing
Pipeline p = container.getPipeline();
if (p != null) {
for (Valve valve : p.getValves()) {
// Skip null valves
if (valve == null) {
continue;
}
// Only compare valves in pipeline until we find this valve
if (valve == this) {
break;
}
if (valve.getClass() == this.getClass()) {
// Duplicate valve earlier in pipeline
// increment sequence number
seq ++;
}
}
}
if (seq > 0) {
name.append(",seq=");
name.append(seq);
}
String className = this.getClass().getName();
int period = className.lastIndexOf('.');
if (period >= 0) {
className = className.substring(period + 1);
}
name.append(",name=");
name.append(className);
return name.toString();
}
@Override
public String getDomainInternal() {
Container c = getContainer();
if (c == null) {
return null;
} else {
return c.getDomain();
}
}
}

View File

@@ -0,0 +1,556 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<mbeans-descriptors>
<mbean name="AccessLogValve"
description="Valve that generates a web server access log"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.AccessLogValve">
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="buffered"
description="Flag to buffering."
is="true"
type="boolean"/>
<attribute name="checkExists"
description="Check for file existence before logging."
is="true"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="condition"
description="The value to look for conditional logging. The same as conditionUnless."
type="java.lang.String"/>
<attribute name="conditionIf"
description="The value to look for conditional logging."
type="java.lang.String"/>
<attribute name="conditionUnless"
description="The value to look for conditional logging."
type="java.lang.String"/>
<attribute name="directory"
description="The directory in which log files are created"
type="java.lang.String"/>
<attribute name="enabled"
description="Enable Access Logging"
is="false"
type="boolean"/>
<attribute name="encoding"
description="Character set used to write the log file"
type="java.lang.String"/>
<attribute name="fileDateFormat"
description="The format for the date for date based log rotation"
type="java.lang.String"/>
<attribute name="locale"
description="The locale used to format timestamps in the access log lines"
type="java.lang.String"/>
<attribute name="pattern"
description="The pattern used to format our access log lines"
type="java.lang.String"/>
<attribute name="prefix"
description="The prefix that is added to log file filenames"
type="java.lang.String"/>
<attribute name="rotatable"
description="Flag to indicate automatic log rotation."
is="true"
type="boolean"/>
<attribute name="renameOnRotate"
description="Flag to defer inclusion of the date stamp in the log file name until rotation."
is="true"
type="boolean"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<attribute name="suffix"
description="The suffix that is added to log file filenames"
type="java.lang.String"/>
<operation name="rotate"
description="Check if the log file is due to be rotated and rotate if it is"
impact="ACTION"
returnType="void">
</operation>
<operation name="rotate"
description="Move the existing log file to a new name"
impact="ACTION"
returnType="boolean">
<parameter name="newFileName"
description="File name to move the log file to."
type="java.lang.String"/>
</operation>
</mbean>
<mbean name="CrawlerSessionManagerValve"
description="Valve that ensures web crawlers always use sessions even if no session ID is presented by the client"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.CrawlerSessionManagerValve">
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="clientIpSessionId"
description="Current Map of client IP address to session ID managed by this Valve"
type="java.util.Map"
writeable="false"/>
<attribute name="crawlerUserAgents"
description="Specify the regular expression used to identify crawlers based in the User-Agent header provided."
type="java.lang.String"
writeable="true"/>
<attribute name="sessionInactiveInterval"
description="Specify the session timeout (in seconds) for a crawler's session."
type="int"
writeable="true"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
</mbean>
<mbean name="ErrorReportValve"
description="Implementation of a Valve that outputs HTML error pages"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.ErrorReportValve">
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<attribute name="showReport"
description="Enables/Disables full error reports"
is="true"
type="boolean"/>
<attribute name="showServerInfo"
description="Enables/Disables server info on error pages"
is="true"
type="boolean"/>
</mbean>
<mbean name="ExtendedAccessLogValve"
description="Valve that generates a web server access log"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.ExtendedAccessLogValve">
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="buffered"
description="Flag to buffering."
is="true"
type="boolean"/>
<attribute name="checkExists"
description="Check for file existence before logging."
is="true"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="condition"
description="The value to look for conditional logging. The same as conditionUnless."
type="java.lang.String"/>
<attribute name="conditionIf"
description="The value to look for conditional logging."
type="java.lang.String"/>
<attribute name="conditionUnless"
description="The value to look for conditional logging."
type="java.lang.String"/>
<attribute name="directory"
description="The directory in which log files are created"
type="java.lang.String"/>
<attribute name="enabled"
description="Enable Access Logging"
is="false"
type="boolean"/>
<attribute name="encoding"
description="Character set used to write the log file"
type="java.lang.String"/>
<attribute name="fileDateFormat"
description="The format for the date date based log rotation."
type="java.lang.String"/>
<attribute name="locale"
description="The locale used to format timestamps in the access log lines"
type="java.lang.String"/>
<attribute name="pattern"
description="The pattern used to format our access log lines"
type="java.lang.String"/>
<attribute name="prefix"
description="The prefix that is added to log file filenames"
type="java.lang.String"/>
<attribute name="rotatable"
description="Flag to indicate automatic log rotation."
is="true"
type="boolean"/>
<attribute name="renameOnRotate"
description="Flag to defer inclusion of the date stamp in the log file name until rotation."
is="true"
type="boolean"/>
<attribute name="suffix"
description="The suffix that is added to log file filenames"
type="java.lang.String"/>
<operation name="rotate"
description="Check if the log file is due to be rotated and rotate if it is"
impact="ACTION"
returnType="void">
</operation>
<operation name="rotate"
description="Move the existing log file to a new name"
impact="ACTION"
returnType="boolean">
<parameter name="newFileName"
description="File name to move the log file to."
type="java.lang.String"/>
</operation>
</mbean>
<mbean name="SemaphoreValve"
description="Valve that does concurrency control"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.SemaphoreValve">
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="block"
description="Should this be blocked until a permit is available?"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="concurrency"
description="Desired concurrency level"
type="int"/>
<attribute name="fairness"
description="Use a fair semaphore"
type="boolean"/>
<attribute name="interruptible"
description="Should this be blocked interruptibly until a permit is available?"
type="boolean"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
</mbean>
<mbean name="RemoteAddrValve"
description="Concrete implementation of RequestFilterValve that filters based on the string representation of the remote client's IP address"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.RemoteAddrValve">
<attribute name="addConnectorPort"
description="Append the server connector port to the client IP separated by a semicolon"
type="boolean"/>
<attribute name="allow"
description="The allow expression"
type="java.lang.String"/>
<attribute name="allowValid"
description="Becomes false if assigned value of allow expression is not syntactically correct"
is="true"
type="boolean"
writeable="false"/>
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="deny"
description="The deny expression"
type="java.lang.String"/>
<attribute name="denyStatus"
description="HTTP response status code that is used when rejecting denied request"
type="int"/>
<attribute name="denyValid"
description="Becomes false if assigned value of deny expression is not syntactically correct"
is="true"
type="boolean"
writeable="false"/>
<attribute name="invalidAuthenticationWhenDeny"
description="Send an invalid authentication header instead of deny"
type="boolean"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<operation name="isAllowed"
description="Tests whether a client with this IP address value is allowed access by the current valve configuration"
impact="INFO"
returnType="boolean">
<parameter name="ipAddress"
description="IP address to be tested"
type="java.lang.String"/>
</operation>
</mbean>
<mbean name="RemoteHostValve"
description="Concrete implementation of RequestFilterValve that filters based on the string representation of the remote client's host name"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.RemoteHostValve">
<attribute name="addConnectorPort"
description="Append the server connector port to the client IP separated by a semicolon"
type="boolean"/>
<attribute name="allow"
description="The allow expression"
type="java.lang.String"/>
<attribute name="allowValid"
description="Becomes false if assigned value of allow expression is not syntactically correct"
is="true"
type="boolean"
writeable="false"/>
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="deny"
description="The deny expression"
type="java.lang.String"/>
<attribute name="denyStatus"
description="HTTP response status code that is used when rejecting denied request"
type="int"/>
<attribute name="denyValid"
description="Becomes false if assigned value of deny expression is not syntactically correct"
is="true"
type="boolean"
writeable="false"/>
<attribute name="invalidAuthenticationWhenDeny"
description="Send an invalid authentication header instead of deny"
type="boolean"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<operation name="isAllowed"
description="Tests whether a client with this host name is allowed access by the current valve configuration"
impact="INFO"
returnType="boolean">
<parameter name="hostName"
description="host name to be tested"
type="java.lang.String"/>
</operation>
</mbean>
<mbean name="RemoteIpValve"
description="Valve that sets client information (eg IP address) based on data from a trusted proxy"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.RemoteIpValve">
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="httpServerPort"
description="Value returned by ServletRequest.getServerPort() when the protocolHeader indicates http protocol"
type="java.lang.String"
writeable="false" />
<attribute name="httpsServerPort"
description="Value returned by ServletRequest.getServerPort() when the protocolHeader indicates https protocol"
type="java.lang.String"
writeable="false" />
<attribute name="internalProxies"
description="Regular expression that matches IP addresses of internal proxies"
type="java.lang.String"
writeable="false" />
<attribute name="protocolHeader"
description="The protocol header (e.g. &quot;X-Forwarded-Proto&quot;)"
type="java.lang.String"
writeable="false" />
<attribute name="protocolHeaderHttpsValue"
description="The value of the protocol header for incoming https request (e.g. &quot;https&quot;)"
type="java.lang.String"
writeable="false" />
<attribute name="proxiesHeader"
description="The proxies header name (e.g. &quot;X-Forwarded-By&quot;)"
type="java.lang.String"
writeable="false" />
<attribute name="remoteIpHeader"
description="The remote IP header name (e.g. &quot;X-Forwarded-For&quot;)"
type="java.lang.String"
writeable="false" />
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<attribute name="trustedProxies"
description="Regular expression that matches IP addresses of trusted proxies"
type="java.lang.String"
writeable="false" />
</mbean>
<mbean name="StuckThreadDetectionValve"
description="Detect long requests for which their thread might be stuck"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.StuckThreadDetectionValve">
<attribute name="asyncSupported"
description="Does this valve support async reporting."
is="true"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<attribute name="stuckThreadCount"
description="Count of the threads currently considered stuck"
type="int"
writeable="false"/>
<attribute name="stuckThreadIds"
description="IDs of the threads currently considered stuck. Each ID can then be used with the Threading MBean to retrieve data about it."
type="long[]"
writeable="false"/>
<attribute name="stuckThreadNames"
description="Names of the threads currently considered stuck."
type="java.lang.String[]"
writeable="false"/>
<attribute name="threshold"
description="Duration in seconds after which a request is considered as stuck"
type="int"/>
</mbean>
</mbeans-descriptors>

View File

@@ -0,0 +1,28 @@
<!--
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.
-->
<body>
<p>This package contains a variety of small Valve implementations that do
not warrant being packaged separately. In addition, there is a convenience
base class (<code>ValveBase</code>) that supports the usual mechanisms for
including custom Valves into the corresponding Pipeline.</p>
<p>Other packages that include Valves include
<code>org.apache.tomcat.logger</code> and
<code>org.apache.tomcat.security</code>.</p>
</body>

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.
rewriteValve.closeError=Error closing configuration
rewriteValve.invalidFlags=Invalid flag in [{0}] flags [{1}]
rewriteValve.invalidLine=Invalid line [{0}]
rewriteValve.invalidMapClassName=Invalid map class name [{0}]
rewriteValve.readError=Error reading configuration

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.
rewriteValve.closeError=Erreur lors de la fermeture de la configuration
rewriteValve.invalidFlags=Indicateur invalide dans [{0}] indicateurs [{1}]
rewriteValve.invalidLine=Ligne invalide [{0}]
rewriteValve.invalidMapClassName=Le nom de la classe [{0}] de la structure est invalide
rewriteValve.readError=Erreur lors de la lecture de la configuration

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.
rewriteValve.closeError=構成を閉じる際のエラー
rewriteValve.invalidFlags=[{0}]に無効なフラグ [{1}]があります。
rewriteValve.invalidLine=無効な行[{0}]
rewriteValve.invalidMapClassName=Mapクラス名[{0}]が無効です。
rewriteValve.readError=構成の読み取りエラー

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.
rewriteValve.closeError=설정 리소스의 스트림을 닫는 중 오류 발생
rewriteValve.invalidFlags=[{0}] 내에 유효하지 않은 플래그입니다 (플래그들: [{1}]).
rewriteValve.invalidLine=유효하지 않은 행 [{0}]
rewriteValve.invalidMapClassName=유효하지 않은 Map 클래스 이름: [{0}]
rewriteValve.readError=설정을 읽는 도중 오류 발생

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.
rewriteValve.readError=读取配置时发生异常

View File

@@ -0,0 +1,48 @@
/*
* 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.catalina.valves.rewrite;
import java.nio.charset.Charset;
/**
* Resolver abstract class.
*/
public abstract class Resolver {
public abstract String resolve(String key);
public String resolveEnv(String key) {
return System.getProperty(key);
}
public abstract String resolveSsl(String key);
public abstract String resolveHttp(String key);
public abstract boolean resolveResource(int type, String name);
/**
* @return The name of the encoding to use to %nn encode URIs
*
* @deprecated This will be removed in Tomcat 9.0.x
*/
@Deprecated
public abstract String getUriEncoding();
public abstract Charset getUriCharset();
}

View File

@@ -0,0 +1,191 @@
/*
* 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.catalina.valves.rewrite;
import java.nio.charset.Charset;
import java.util.Calendar;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.connector.Request;
import org.apache.tomcat.util.http.FastHttpDateFormat;
public class ResolverImpl extends Resolver {
protected Request request = null;
public ResolverImpl(Request request) {
this.request = request;
}
/**
* The following are not implemented:
* - SERVER_ADMIN
* - API_VERSION
* - IS_SUBREQ
*/
@Override
public String resolve(String key) {
if (key.equals("HTTP_USER_AGENT")) {
return request.getHeader("user-agent");
} else if (key.equals("HTTP_REFERER")) {
return request.getHeader("referer");
} else if (key.equals("HTTP_COOKIE")) {
return request.getHeader("cookie");
} else if (key.equals("HTTP_FORWARDED")) {
return request.getHeader("forwarded");
} else if (key.equals("HTTP_HOST")) {
String host = request.getHeader("host");
if (host != null) {
int index = host.indexOf(':');
if (index != -1) {
host = host.substring(0, index);
}
}
return host;
} else if (key.equals("HTTP_PROXY_CONNECTION")) {
return request.getHeader("proxy-connection");
} else if (key.equals("HTTP_ACCEPT")) {
return request.getHeader("accept");
} else if (key.equals("REMOTE_ADDR")) {
return request.getRemoteAddr();
} else if (key.equals("REMOTE_HOST")) {
return request.getRemoteHost();
} else if (key.equals("REMOTE_PORT")) {
return String.valueOf(request.getRemotePort());
} else if (key.equals("REMOTE_USER")) {
return request.getRemoteUser();
} else if (key.equals("REMOTE_IDENT")) {
return request.getRemoteUser();
} else if (key.equals("REQUEST_METHOD")) {
return request.getMethod();
} else if (key.equals("SCRIPT_FILENAME")) {
return request.getServletContext().getRealPath(request.getServletPath());
} else if (key.equals("REQUEST_PATH")) {
return request.getRequestPathMB().toString();
} else if (key.equals("CONTEXT_PATH")) {
return request.getContextPath();
} else if (key.equals("SERVLET_PATH")) {
return emptyStringIfNull(request.getServletPath());
} else if (key.equals("PATH_INFO")) {
return emptyStringIfNull(request.getPathInfo());
} else if (key.equals("QUERY_STRING")) {
return emptyStringIfNull(request.getQueryString());
} else if (key.equals("AUTH_TYPE")) {
return request.getAuthType();
} else if (key.equals("DOCUMENT_ROOT")) {
return request.getServletContext().getRealPath("/");
} else if (key.equals("SERVER_NAME")) {
return request.getLocalName();
} else if (key.equals("SERVER_ADDR")) {
return request.getLocalAddr();
} else if (key.equals("SERVER_PORT")) {
return String.valueOf(request.getLocalPort());
} else if (key.equals("SERVER_PROTOCOL")) {
return request.getProtocol();
} else if (key.equals("SERVER_SOFTWARE")) {
return "tomcat";
} else if (key.equals("THE_REQUEST")) {
return request.getMethod() + " " + request.getRequestURI()
+ " " + request.getProtocol();
} else if (key.equals("REQUEST_URI")) {
return request.getRequestURI();
} else if (key.equals("REQUEST_FILENAME")) {
return request.getPathTranslated();
} else if (key.equals("HTTPS")) {
return request.isSecure() ? "on" : "off";
} else if (key.equals("TIME_YEAR")) {
return String.valueOf(Calendar.getInstance().get(Calendar.YEAR));
} else if (key.equals("TIME_MON")) {
return String.valueOf(Calendar.getInstance().get(Calendar.MONTH));
} else if (key.equals("TIME_DAY")) {
return String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
} else if (key.equals("TIME_HOUR")) {
return String.valueOf(Calendar.getInstance().get(Calendar.HOUR_OF_DAY));
} else if (key.equals("TIME_MIN")) {
return String.valueOf(Calendar.getInstance().get(Calendar.MINUTE));
} else if (key.equals("TIME_SEC")) {
return String.valueOf(Calendar.getInstance().get(Calendar.SECOND));
} else if (key.equals("TIME_WDAY")) {
return String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_WEEK));
} else if (key.equals("TIME")) {
return FastHttpDateFormat.getCurrentDate();
}
return null;
}
@Override
public String resolveEnv(String key) {
Object result = request.getAttribute(key);
return (result != null) ? result.toString() : System.getProperty(key);
}
@Override
public String resolveSsl(String key) {
// FIXME: Implement SSL environment variables
return null;
}
@Override
public String resolveHttp(String key) {
String header = request.getHeader(key);
if (header == null) {
return "";
} else {
return header;
}
}
@Override
public boolean resolveResource(int type, String name) {
WebResourceRoot resources = request.getContext().getResources();
WebResource resource = resources.getResource(name);
if (!resource.exists()) {
return false;
} else {
switch (type) {
case 0:
return resource.isDirectory();
case 1:
return resource.isFile();
case 2:
return resource.isFile() && resource.getContentLength() > 0;
default:
return false;
}
}
}
private static final String emptyStringIfNull(String value) {
if (value == null) {
return "";
} else {
return value;
}
}
@Override
@Deprecated
public String getUriEncoding() {
return request.getConnector().getURIEncoding();
}
@Override
public Charset getUriCharset() {
return request.getConnector().getURICharset();
}
}

View File

@@ -0,0 +1,239 @@
/*
* 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.catalina.valves.rewrite;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RewriteCond {
public abstract static class Condition {
public abstract boolean evaluate(String value, Resolver resolver);
}
public static class PatternCondition extends Condition {
public Pattern pattern;
private ThreadLocal<Matcher> matcher = new ThreadLocal<>();
@Override
public boolean evaluate(String value, Resolver resolver) {
Matcher m = pattern.matcher(value);
if (m.matches()) {
matcher.set(m);
return true;
} else {
return false;
}
}
public Matcher getMatcher() {
return matcher.get();
}
}
public static class LexicalCondition extends Condition {
/**
* -1: &lt;
* 0: =
* 1: &gt;
*/
public int type = 0;
public String condition;
@Override
public boolean evaluate(String value, Resolver resolver) {
int result = value.compareTo(condition);
switch (type) {
case -1:
return (result < 0);
case 0:
return (result == 0);
case 1:
return (result > 0);
default:
return false;
}
}
}
public static class ResourceCondition extends Condition {
/**
* 0: -d (is directory ?)
* 1: -f (is regular file ?)
* 2: -s (is regular file with size ?)
*/
public int type = 0;
@Override
public boolean evaluate(String value, Resolver resolver) {
return resolver.resolveResource(type, value);
}
}
protected String testString = null;
protected String condPattern = null;
protected String flagsString = null;
public String getCondPattern() {
return condPattern;
}
public void setCondPattern(String condPattern) {
this.condPattern = condPattern;
}
public String getTestString() {
return testString;
}
public void setTestString(String testString) {
this.testString = testString;
}
public final String getFlagsString() {
return flagsString;
}
public final void setFlagsString(String flagsString) {
this.flagsString = flagsString;
}
public void parse(Map<String, RewriteMap> maps) {
test = new Substitution();
test.setSub(testString);
test.parse(maps);
if (condPattern.startsWith("!")) {
positive = false;
condPattern = condPattern.substring(1);
}
if (condPattern.startsWith("<")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = -1;
ncondition.condition = condPattern.substring(1);
this.condition = ncondition;
} else if (condPattern.startsWith(">")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = 1;
ncondition.condition = condPattern.substring(1);
this.condition = ncondition;
} else if (condPattern.startsWith("=")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = 0;
ncondition.condition = condPattern.substring(1);
this.condition = ncondition;
} else if (condPattern.equals("-d")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 0;
this.condition = ncondition;
} else if (condPattern.equals("-f")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 1;
this.condition = ncondition;
} else if (condPattern.equals("-s")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 2;
this.condition = ncondition;
} else {
PatternCondition ncondition = new PatternCondition();
int flags = 0;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
ncondition.pattern = Pattern.compile(condPattern, flags);
this.condition = ncondition;
}
}
public Matcher getMatcher() {
if (condition instanceof PatternCondition) {
return ((PatternCondition) condition).getMatcher();
}
return null;
}
/**
* String representation.
*/
@Override
public String toString() {
return "RewriteCond " + testString + " " + condPattern
+ ((flagsString != null) ? (" " + flagsString) : "");
}
protected boolean positive = true;
protected Substitution test = null;
protected Condition condition = null;
/**
* This makes the test case-insensitive, i.e., there is no difference between
* 'A-Z' and 'a-z' both in the expanded TestString and the CondPattern. This
* flag is effective only for comparisons between TestString and CondPattern.
* It has no effect on filesystem and subrequest checks.
*/
public boolean nocase = false;
/**
* Use this to combine rule conditions with a local OR instead of the implicit AND.
*/
public boolean ornext = false;
/**
* Evaluate the condition based on the context
*
* @param rule corresponding matched rule
* @param cond last matched condition
* @param resolver Property resolver
* @return <code>true</code> if the condition matches
*/
public boolean evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String value = test.evaluate(rule, cond, resolver);
if (positive) {
return condition.evaluate(value, resolver);
} else {
return !condition.evaluate(value, resolver);
}
}
public boolean isNocase() {
return nocase;
}
public void setNocase(boolean nocase) {
this.nocase = nocase;
}
public boolean isOrnext() {
return ornext;
}
public void setOrnext(boolean ornext) {
this.ornext = ornext;
}
public boolean isPositive() {
return positive;
}
public void setPositive(boolean positive) {
this.positive = positive;
}
}

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.
*/
package org.apache.catalina.valves.rewrite;
public interface RewriteMap {
public String setParameters(String params);
public String lookup(String key);
}

View File

@@ -0,0 +1,563 @@
/*
* 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.catalina.valves.rewrite;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RewriteRule {
protected RewriteCond[] conditions = new RewriteCond[0];
protected ThreadLocal<Pattern> pattern = new ThreadLocal<>();
protected Substitution substitution = null;
protected String patternString = null;
protected String substitutionString = null;
protected String flagsString = null;
protected boolean positive = true;
public void parse(Map<String, RewriteMap> maps) {
// Parse the substitution
if (!"-".equals(substitutionString)) {
substitution = new Substitution();
substitution.setSub(substitutionString);
substitution.parse(maps);
substitution.setEscapeBackReferences(isEscapeBackReferences());
}
// Parse the pattern
if (patternString.startsWith("!")) {
positive = false;
patternString = patternString.substring(1);
}
int flags = 0;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
Pattern.compile(patternString, flags);
// Parse conditions
for (int i = 0; i < conditions.length; i++) {
conditions[i].parse(maps);
}
// Parse flag which have substitution values
if (isEnv()) {
for (int i = 0; i < envValue.size(); i++) {
Substitution newEnvSubstitution = new Substitution();
newEnvSubstitution.setSub(envValue.get(i));
newEnvSubstitution.parse(maps);
envSubstitution.add(newEnvSubstitution);
envResult.add(new ThreadLocal<String>());
}
}
if (isCookie()) {
cookieSubstitution = new Substitution();
cookieSubstitution.setSub(cookieValue);
cookieSubstitution.parse(maps);
}
}
public void addCondition(RewriteCond condition) {
RewriteCond[] conditions = Arrays.copyOf(this.conditions, this.conditions.length + 1);
conditions[this.conditions.length] = condition;
this.conditions = conditions;
}
/**
* Evaluate the rule based on the context
* @param url The char sequence
* @param resolver Property resolver
* @return <code>null</code> if no rewrite took place
*/
public CharSequence evaluate(CharSequence url, Resolver resolver) {
Pattern pattern = this.pattern.get();
if (pattern == null) {
// Parse the pattern
int flags = 0;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
pattern = Pattern.compile(patternString, flags);
this.pattern.set(pattern);
}
Matcher matcher = pattern.matcher(url);
// Use XOR
if (positive ^ matcher.matches()) {
// Evaluation done
return null;
}
// Evaluate conditions
boolean done = false;
boolean rewrite = true;
Matcher lastMatcher = null;
int pos = 0;
while (!done) {
if (pos < conditions.length) {
rewrite = conditions[pos].evaluate(matcher, lastMatcher, resolver);
if (rewrite) {
Matcher lastMatcher2 = conditions[pos].getMatcher();
if (lastMatcher2 != null) {
lastMatcher = lastMatcher2;
}
while (pos < conditions.length && conditions[pos].isOrnext()) {
pos++;
}
} else if (!conditions[pos].isOrnext()) {
done = true;
}
pos++;
} else {
done = true;
}
}
// Use the substitution to rewrite the url
if (rewrite) {
if (isEnv()) {
for (int i = 0; i < envSubstitution.size(); i++) {
envResult.get(i).set(envSubstitution.get(i).evaluate(matcher, lastMatcher, resolver));
}
}
if (isCookie()) {
cookieResult.set(cookieSubstitution.evaluate(matcher, lastMatcher, resolver));
}
if (substitution != null) {
return substitution.evaluate(matcher, lastMatcher, resolver);
} else {
return url;
}
} else {
return null;
}
}
/**
* String representation.
*/
@Override
public String toString() {
return "RewriteRule " + patternString + " " + substitutionString
+ ((flagsString != null) ? (" " + flagsString) : "");
}
private boolean escapeBackReferences = false;
/**
* This flag chains the current rule with the next rule (which itself
* can be chained with the following rule, etc.). This has the following
* effect: if a rule matches, then processing continues as usual, i.e.,
* the flag has no effect. If the rule does not match, then all following
* chained rules are skipped. For instance, use it to remove the ``.www''
* part inside a per-directory rule set when you let an external redirect
* happen (where the ``.www'' part should not to occur!).
*/
protected boolean chain = false;
/**
* This sets a cookie on the client's browser. The cookie's name is
* specified by NAME and the value is VAL. The domain field is the domain
* of the cookie, such as '.apache.org',the optional lifetime
* is the lifetime of the cookie in minutes, and the optional path is the
* path of the cookie
*/
protected boolean cookie = false;
protected String cookieName = null;
protected String cookieValue = null;
protected String cookieDomain = null;
protected int cookieLifetime = -1;
protected String cookiePath = null;
protected boolean cookieSecure = false;
protected boolean cookieHttpOnly = false;
protected Substitution cookieSubstitution = null;
protected ThreadLocal<String> cookieResult = new ThreadLocal<>();
/**
* This forces a request attribute named VAR to be set to the value VAL,
* where VAL can contain regexp back references $N and %N which will be
* expanded. Multiple env flags are allowed.
*/
protected boolean env = false;
protected ArrayList<String> envName = new ArrayList<>();
protected ArrayList<String> envValue = new ArrayList<>();
protected ArrayList<Substitution> envSubstitution = new ArrayList<>();
protected ArrayList<ThreadLocal<String>> envResult = new ArrayList<>();
/**
* This forces the current URL to be forbidden, i.e., it immediately sends
* back an HTTP response of 403 (FORBIDDEN). Use this flag in conjunction
* with appropriate RewriteConds to conditionally block some URLs.
*/
protected boolean forbidden = false;
/**
* This forces the current URL to be gone, i.e., it immediately sends
* back an HTTP response of 410 (GONE). Use this flag to mark pages which
* no longer exist as gone.
*/
protected boolean gone = false;
/**
* Host. This means this rule and its associated conditions will apply to
* host, allowing host rewriting (ex: redirecting internally *.foo.com to
* bar.foo.com).
*/
protected boolean host = false;
/**
* Stop the rewriting process here and don't apply any more rewriting
* rules. This corresponds to the Perl last command or the break command
* from the C language. Use this flag to prevent the currently rewritten
* URL from being rewritten further by following rules. For example, use
* it to rewrite the root-path URL ('/') to a real one, e.g., '/e/www/'.
*/
protected boolean last = false;
/**
* Re-run the rewriting process (starting again with the first rewriting
* rule). Here the URL to match is again not the original URL but the URL
* from the last rewriting rule. This corresponds to the Perl next
* command or the continue command from the C language. Use this flag to
* restart the rewriting process, i.e., to immediately go to the top of
* the loop. But be careful not to create an infinite loop!
*/
protected boolean next = false;
/**
* This makes the Pattern case-insensitive, i.e., there is no difference
* between 'A-Z' and 'a-z' when Pattern is matched against the current
* URL.
*/
protected boolean nocase = false;
/**
* This flag keeps mod_rewrite from applying the usual URI escaping rules
* to the result of a rewrite. Ordinarily, special characters (such as
* '%', '$', ';', and so on) will be escaped into their hexcode
* equivalents ('%25', '%24', and '%3B', respectively); this flag
* prevents this from being done. This allows percent symbols to appear
* in the output, as in
* RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE]
* which would turn '/foo/zed' into a safe request for '/bar?arg=P1=zed'.
*/
protected boolean noescape = false;
/**
* This flag forces the rewriting engine to skip a rewriting rule if the
* current request is an internal sub-request. For instance, sub-requests
* occur internally in Apache when mod_include tries to find out
* information about possible directory default files (index.xxx). On
* sub-requests it is not always useful and even sometimes causes a
* failure to if the complete set of rules are applied. Use this flag to
* exclude some rules. Use the following rule for your decision: whenever
* you prefix some URLs with CGI-scripts to force them to be processed by
* the CGI-script, the chance is high that you will run into problems (or
* even overhead) on sub-requests. In these cases, use this flag.
*/
protected boolean nosubreq = false;
/**
* Note: No proxy
*/
/**
* Note: No passthrough
*/
/**
* This flag forces the rewriting engine to append a query string part in
* the substitution string to the existing one instead of replacing it.
* Use this when you want to add more data to the query string via
* a rewrite rule.
*/
protected boolean qsappend = false;
/**
* When the requested URI contains a query string, and the target URI does
* not, the default behavior of RewriteRule is to copy that query string
* to the target URI. Using the [QSD] flag causes the query string
* to be discarded.
* Using [QSD] and [QSA] together will result in [QSD] taking precedence.
*/
protected boolean qsdiscard = false;
/**
* Prefix Substitution with http://thishost[:thisport]/ (which makes the
* new URL a URI) to force a external redirection. If no code is given
* an HTTP response of 302 (FOUND, previously MOVED TEMPORARILY) is used.
* If you want to use other response codes in the range 300-399 just
* specify them as a number or use one of the following symbolic names:
* temp (default), permanent, seeother. Use it for rules which should
* canonicalize the URL and give it back to the client, e.g., translate
* ``/~'' into ``/u/'' or always append a slash to /u/user, etc. Note:
* When you use this flag, make sure that the substitution field is a
* valid URL! If not, you are redirecting to an invalid location!
* And remember that this flag itself only prefixes the URL with
* http://thishost[:thisport]/, rewriting continues. Usually you also
* want to stop and do the redirection immediately. To stop the
* rewriting you also have to provide the 'L' flag.
*/
protected boolean redirect = false;
protected int redirectCode = 0;
/**
* This flag forces the rewriting engine to skip the next num rules in
* sequence when the current rule matches. Use this to make pseudo
* if-then-else constructs: The last rule of the then-clause becomes
* skip=N where N is the number of rules in the else-clause.
* (This is not the same as the 'chain|C' flag!)
*/
protected int skip = 0;
/**
* Force the MIME-type of the target file to be MIME-type. For instance,
* this can be used to setup the content-type based on some conditions.
* For example, the following snippet allows .php files to be displayed
* by mod_php if they are called with the .phps extension:
* RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source]
*/
protected boolean type = false;
protected String typeValue = null;
public boolean isEscapeBackReferences() {
return escapeBackReferences;
}
public void setEscapeBackReferences(boolean escapeBackReferences) {
this.escapeBackReferences = escapeBackReferences;
}
public boolean isChain() {
return chain;
}
public void setChain(boolean chain) {
this.chain = chain;
}
public RewriteCond[] getConditions() {
return conditions;
}
public void setConditions(RewriteCond[] conditions) {
this.conditions = conditions;
}
public boolean isCookie() {
return cookie;
}
public void setCookie(boolean cookie) {
this.cookie = cookie;
}
public String getCookieName() {
return cookieName;
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
}
public String getCookieValue() {
return cookieValue;
}
public void setCookieValue(String cookieValue) {
this.cookieValue = cookieValue;
}
public String getCookieResult() {
return cookieResult.get();
}
public boolean isEnv() {
return env;
}
public int getEnvSize() {
return envName.size();
}
public void setEnv(boolean env) {
this.env = env;
}
public String getEnvName(int i) {
return envName.get(i);
}
public void addEnvName(String envName) {
this.envName.add(envName);
}
public String getEnvValue(int i) {
return envValue.get(i);
}
public void addEnvValue(String envValue) {
this.envValue.add(envValue);
}
public String getEnvResult(int i) {
return envResult.get(i).get();
}
public boolean isForbidden() {
return forbidden;
}
public void setForbidden(boolean forbidden) {
this.forbidden = forbidden;
}
public boolean isGone() {
return gone;
}
public void setGone(boolean gone) {
this.gone = gone;
}
public boolean isLast() {
return last;
}
public void setLast(boolean last) {
this.last = last;
}
public boolean isNext() {
return next;
}
public void setNext(boolean next) {
this.next = next;
}
public boolean isNocase() {
return nocase;
}
public void setNocase(boolean nocase) {
this.nocase = nocase;
}
public boolean isNoescape() {
return noescape;
}
public void setNoescape(boolean noescape) {
this.noescape = noescape;
}
public boolean isNosubreq() {
return nosubreq;
}
public void setNosubreq(boolean nosubreq) {
this.nosubreq = nosubreq;
}
public boolean isQsappend() {
return qsappend;
}
public void setQsappend(boolean qsappend) {
this.qsappend = qsappend;
}
public final boolean isQsdiscard() {
return qsdiscard;
}
public final void setQsdiscard(boolean qsdiscard) {
this.qsdiscard = qsdiscard;
}
public boolean isRedirect() {
return redirect;
}
public void setRedirect(boolean redirect) {
this.redirect = redirect;
}
public int getRedirectCode() {
return redirectCode;
}
public void setRedirectCode(int redirectCode) {
this.redirectCode = redirectCode;
}
public int getSkip() {
return skip;
}
public void setSkip(int skip) {
this.skip = skip;
}
public Substitution getSubstitution() {
return substitution;
}
public void setSubstitution(Substitution substitution) {
this.substitution = substitution;
}
public boolean isType() {
return type;
}
public void setType(boolean type) {
this.type = type;
}
public String getTypeValue() {
return typeValue;
}
public void setTypeValue(String typeValue) {
this.typeValue = typeValue;
}
public String getPatternString() {
return patternString;
}
public void setPatternString(String patternString) {
this.patternString = patternString;
}
public String getSubstitutionString() {
return substitutionString;
}
public void setSubstitutionString(String substitutionString) {
this.substitutionString = substitutionString;
}
public final String getFlagsString() {
return flagsString;
}
public final void setFlagsString(String flagsString) {
this.flagsString = flagsString;
}
public boolean isHost() {
return host;
}
public void setHost(boolean host) {
this.host = host;
}
public String getCookieDomain() {
return cookieDomain;
}
public void setCookieDomain(String cookieDomain) {
this.cookieDomain = cookieDomain;
}
public int getCookieLifetime() {
return cookieLifetime;
}
public void setCookieLifetime(int cookieLifetime) {
this.cookieLifetime = cookieLifetime;
}
public String getCookiePath() {
return cookiePath;
}
public void setCookiePath(String cookiePath) {
this.cookiePath = cookiePath;
}
public boolean isCookieSecure() {
return cookieSecure;
}
public void setCookieSecure(boolean cookieSecure) {
this.cookieSecure = cookieSecure;
}
public boolean isCookieHttpOnly() {
return cookieHttpOnly;
}
public void setCookieHttpOnly(boolean cookieHttpOnly) {
this.cookieHttpOnly = cookieHttpOnly;
}
}

View File

@@ -0,0 +1,845 @@
/*
* 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.catalina.valves.rewrite;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Pipeline;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.URLEncoder;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.CharChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.buf.UDecoder;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.http.RequestUtil;
/**
* Note: Extra caution should be used when adding a Rewrite Rule. When
* specifying a regex to match for in a Rewrite Rule, certain regex could allow
* an attacker to DoS your server, as Java's regex parsing is vulnerable to
* "catastrophic backtracking" (also known as "Regular expression Denial of
* Service", or ReDoS). There are some open source tools to help detect
* vulnerable regex, though in general it is a hard problem. A good defence is
* to use a regex debugger on your desired regex, and read more on the subject
* of catastrophic backtracking.
*
* @see <a href=
* "https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">OWASP
* ReDoS</a>
*/
public class RewriteValve extends ValveBase {
/**
* The rewrite rules that the valve will use.
*/
protected RewriteRule[] rules = null;
/**
* If rewriting occurs, the whole request will be processed again.
*/
protected ThreadLocal<Boolean> invoked = new ThreadLocal<>();
/**
* Relative path to the configuration file.
* Note: If the valve's container is a context, this will be relative to
* /WEB-INF/.
*/
protected String resourcePath = "rewrite.config";
/**
* Will be set to true if the valve is associated with a context.
*/
protected boolean context = false;
/**
* enabled this component
*/
protected boolean enabled = true;
/**
* Maps to be used by the rules.
*/
protected Map<String, RewriteMap> maps = new Hashtable<>();
/**
* Maps configuration.
*/
protected ArrayList<String> mapsConfiguration = new ArrayList<>();
public RewriteValve() {
super(true);
}
public boolean getEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
containerLog = LogFactory.getLog(getContainer().getLogName() + ".rewrite");
}
@Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
InputStream is = null;
// Process configuration file for this valve
if (getContainer() instanceof Context) {
context = true;
is = ((Context) getContainer()).getServletContext()
.getResourceAsStream("/WEB-INF/" + resourcePath);
if (containerLog.isDebugEnabled()) {
if (is == null) {
containerLog.debug("No configuration resource found: /WEB-INF/" + resourcePath);
} else {
containerLog.debug("Read configuration from: /WEB-INF/" + resourcePath);
}
}
} else if (getContainer() instanceof Host) {
String resourceName = getHostConfigPath(resourcePath);
File file = new File(getConfigBase(), resourceName);
try {
if (!file.exists()) {
// Use getResource and getResourceAsStream
is = getClass().getClassLoader()
.getResourceAsStream(resourceName);
if (is != null && containerLog.isDebugEnabled()) {
containerLog.debug("Read configuration from CL at " + resourceName);
}
} else {
if (containerLog.isDebugEnabled()) {
containerLog.debug("Read configuration from " + file.getAbsolutePath());
}
is = new FileInputStream(file);
}
if ((is == null) && (containerLog.isDebugEnabled())) {
containerLog.debug("No configuration resource found: " + resourceName +
" in " + getConfigBase() + " or in the classloader");
}
} catch (Exception e) {
containerLog.error("Error opening configuration", e);
}
}
if (is == null) {
// Will use management operations to configure the valve dynamically
return;
}
try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr)) {
parse(reader);
} catch (IOException ioe) {
containerLog.error(sm.getString("rewriteValve.closeError"), ioe);
} finally {
try {
is.close();
} catch (IOException e) {
containerLog.error(sm.getString("rewriteValve.closeError"), e);
}
}
}
public void setConfiguration(String configuration)
throws Exception {
if (containerLog == null) {
containerLog = LogFactory.getLog(getContainer().getLogName() + ".rewrite");
}
maps.clear();
parse(new BufferedReader(new StringReader(configuration)));
}
public String getConfiguration() {
StringBuffer buffer = new StringBuffer();
for (String mapConfiguration : mapsConfiguration) {
buffer.append(mapConfiguration).append("\r\n");
}
if (mapsConfiguration.size() > 0) {
buffer.append("\r\n");
}
for (int i = 0; i < rules.length; i++) {
for (int j = 0; j < rules[i].getConditions().length; j++) {
buffer.append(rules[i].getConditions()[j].toString()).append("\r\n");
}
buffer.append(rules[i].toString()).append("\r\n").append("\r\n");
}
return buffer.toString();
}
protected void parse(BufferedReader reader) throws LifecycleException {
List<RewriteRule> rules = new ArrayList<>();
List<RewriteCond> conditions = new ArrayList<>();
while (true) {
try {
String line = reader.readLine();
if (line == null) {
break;
}
Object result = parse(line);
if (result instanceof RewriteRule) {
RewriteRule rule = (RewriteRule) result;
if (containerLog.isDebugEnabled()) {
containerLog.debug("Add rule with pattern " + rule.getPatternString()
+ " and substitution " + rule.getSubstitutionString());
}
for (int i = (conditions.size() - 1); i > 0; i--) {
if (conditions.get(i - 1).isOrnext()) {
conditions.get(i).setOrnext(true);
}
}
for (int i = 0; i < conditions.size(); i++) {
if (containerLog.isDebugEnabled()) {
RewriteCond cond = conditions.get(i);
containerLog.debug("Add condition " + cond.getCondPattern()
+ " test " + cond.getTestString() + " to rule with pattern "
+ rule.getPatternString() + " and substitution "
+ rule.getSubstitutionString() + (cond.isOrnext() ? " [OR]" : "")
+ (cond.isNocase() ? " [NC]" : ""));
}
rule.addCondition(conditions.get(i));
}
conditions.clear();
rules.add(rule);
} else if (result instanceof RewriteCond) {
conditions.add((RewriteCond) result);
} else if (result instanceof Object[]) {
String mapName = (String) ((Object[]) result)[0];
RewriteMap map = (RewriteMap) ((Object[]) result)[1];
maps.put(mapName, map);
// Keep the original configuration line as it is not possible to get
// the parameters back without an API change
mapsConfiguration.add(line);
if (map instanceof Lifecycle) {
((Lifecycle) map).start();
}
}
} catch (IOException e) {
containerLog.error(sm.getString("rewriteValve.readError"), e);
}
}
this.rules = rules.toArray(new RewriteRule[0]);
// Finish parsing the rules
for (int i = 0; i < this.rules.length; i++) {
this.rules[i].parse(maps);
}
}
@Override
protected synchronized void stopInternal() throws LifecycleException {
super.stopInternal();
for (RewriteMap map : maps.values()) {
if (map instanceof Lifecycle) {
((Lifecycle) map).stop();
}
}
maps.clear();
rules = null;
}
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
if (!getEnabled() || rules == null || rules.length == 0) {
getNext().invoke(request, response);
return;
}
if (Boolean.TRUE.equals(invoked.get())) {
try {
getNext().invoke(request, response);
} finally {
invoked.set(null);
}
return;
}
try {
Resolver resolver = new ResolverImpl(request);
invoked.set(Boolean.TRUE);
// As long as MB isn't a char sequence or affiliated, this has to be
// converted to a string
Charset uriCharset = request.getConnector().getURICharset();
String originalQueryStringEncoded = request.getQueryString();
MessageBytes urlMB =
context ? request.getRequestPathMB() : request.getDecodedRequestURIMB();
urlMB.toChars();
CharSequence urlDecoded = urlMB.getCharChunk();
CharSequence host = request.getServerName();
boolean rewritten = false;
boolean done = false;
boolean qsa = false;
boolean qsd = false;
for (int i = 0; i < rules.length; i++) {
RewriteRule rule = rules[i];
CharSequence test = (rule.isHost()) ? host : urlDecoded;
CharSequence newtest = rule.evaluate(test, resolver);
if (newtest != null && !test.equals(newtest.toString())) {
if (containerLog.isDebugEnabled()) {
containerLog.debug("Rewrote " + test + " as " + newtest
+ " with rule pattern " + rule.getPatternString());
}
if (rule.isHost()) {
host = newtest;
} else {
urlDecoded = newtest;
}
rewritten = true;
}
// Check QSA before the final reply
if (!qsa && newtest != null && rule.isQsappend()) {
qsa = true;
}
if (!qsa && newtest != null && rule.isQsdiscard()) {
qsd = true;
}
// Final reply
// - forbidden
if (rule.isForbidden() && newtest != null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
done = true;
break;
}
// - gone
if (rule.isGone() && newtest != null) {
response.sendError(HttpServletResponse.SC_GONE);
done = true;
break;
}
// - redirect (code)
if (rule.isRedirect() && newtest != null) {
// Append the query string to the url if there is one and it
// hasn't been rewritten
String urlStringDecoded = urlDecoded.toString();
int index = urlStringDecoded.indexOf("?");
String rewrittenQueryStringDecoded;
if (index == -1) {
rewrittenQueryStringDecoded = null;
} else {
rewrittenQueryStringDecoded = urlStringDecoded.substring(index + 1);
urlStringDecoded = urlStringDecoded.substring(0, index);
}
StringBuffer urlStringEncoded =
new StringBuffer(URLEncoder.DEFAULT.encode(urlStringDecoded, uriCharset));
if (!qsd && originalQueryStringEncoded != null
&& originalQueryStringEncoded.length() > 0) {
if (rewrittenQueryStringDecoded == null) {
urlStringEncoded.append('?');
urlStringEncoded.append(originalQueryStringEncoded);
} else {
if (qsa) {
// if qsa is specified append the query
urlStringEncoded.append('?');
urlStringEncoded.append(URLEncoder.QUERY.encode(
rewrittenQueryStringDecoded, uriCharset));
urlStringEncoded.append('&');
urlStringEncoded.append(originalQueryStringEncoded);
} else if (index == urlStringEncoded.length() - 1) {
// if the ? is the last character delete it, its only purpose was to
// prevent the rewrite module from appending the query string
urlStringEncoded.deleteCharAt(index);
} else {
urlStringEncoded.append('?');
urlStringEncoded.append(URLEncoder.QUERY.encode(
rewrittenQueryStringDecoded, uriCharset));
}
}
} else if (rewrittenQueryStringDecoded != null) {
urlStringEncoded.append('?');
urlStringEncoded.append(
URLEncoder.QUERY.encode(rewrittenQueryStringDecoded, uriCharset));
}
// Insert the context if
// 1. this valve is associated with a context
// 2. the url starts with a leading slash
// 3. the url isn't absolute
if (context && urlStringEncoded.charAt(0) == '/' &&
!UriUtil.hasScheme(urlStringEncoded)) {
urlStringEncoded.insert(0, request.getContext().getEncodedPath());
}
if (rule.isNoescape()) {
response.sendRedirect(
UDecoder.URLDecode(urlStringEncoded.toString(), uriCharset));
} else {
response.sendRedirect(urlStringEncoded.toString());
}
response.setStatus(rule.getRedirectCode());
done = true;
break;
}
// Reply modification
// - cookie
if (rule.isCookie() && newtest != null) {
Cookie cookie = new Cookie(rule.getCookieName(),
rule.getCookieResult());
cookie.setDomain(rule.getCookieDomain());
cookie.setMaxAge(rule.getCookieLifetime());
cookie.setPath(rule.getCookiePath());
cookie.setSecure(rule.isCookieSecure());
cookie.setHttpOnly(rule.isCookieHttpOnly());
response.addCookie(cookie);
}
// - env (note: this sets a request attribute)
if (rule.isEnv() && newtest != null) {
for (int j = 0; j < rule.getEnvSize(); j++) {
request.setAttribute(rule.getEnvName(j), rule.getEnvResult(j));
}
}
// - content type (note: this will not force the content type, use a filter
// to do that)
if (rule.isType() && newtest != null) {
request.setContentType(rule.getTypeValue());
}
// Control flow processing
// - chain (skip remaining chained rules if this one does not match)
if (rule.isChain() && newtest == null) {
for (int j = i; j < rules.length; j++) {
if (!rules[j].isChain()) {
i = j;
break;
}
}
continue;
}
// - last (stop rewriting here)
if (rule.isLast() && newtest != null) {
break;
}
// - next (redo again)
if (rule.isNext() && newtest != null) {
i = 0;
continue;
}
// - skip (n rules)
if (newtest != null) {
i += rule.getSkip();
}
}
if (rewritten) {
if (!done) {
// See if we need to replace the query string
String urlStringDecoded = urlDecoded.toString();
String queryStringDecoded = null;
int queryIndex = urlStringDecoded.indexOf('?');
if (queryIndex != -1) {
queryStringDecoded = urlStringDecoded.substring(queryIndex+1);
urlStringDecoded = urlStringDecoded.substring(0, queryIndex);
}
// Save the current context path before re-writing starts
String contextPath = null;
if (context) {
contextPath = request.getContextPath();
}
// Populated the encoded (i.e. undecoded) requestURI
request.getCoyoteRequest().requestURI().setString(null);
CharChunk chunk = request.getCoyoteRequest().requestURI().getCharChunk();
chunk.recycle();
if (context) {
// This is neither decoded nor normalized
chunk.append(contextPath);
}
chunk.append(URLEncoder.DEFAULT.encode(urlStringDecoded, uriCharset));
request.getCoyoteRequest().requestURI().toChars();
// Decoded and normalized URI
// Rewriting may have denormalized the URL
urlStringDecoded = RequestUtil.normalize(urlStringDecoded);
request.getCoyoteRequest().decodedURI().setString(null);
chunk = request.getCoyoteRequest().decodedURI().getCharChunk();
chunk.recycle();
if (context) {
// This is decoded and normalized
chunk.append(request.getServletContext().getContextPath());
}
chunk.append(urlStringDecoded);
request.getCoyoteRequest().decodedURI().toChars();
// Set the new Query if there is one
if (queryStringDecoded != null) {
request.getCoyoteRequest().queryString().setString(null);
chunk = request.getCoyoteRequest().queryString().getCharChunk();
chunk.recycle();
chunk.append(URLEncoder.QUERY.encode(queryStringDecoded, uriCharset));
if (qsa && originalQueryStringEncoded != null &&
originalQueryStringEncoded.length() > 0) {
chunk.append('&');
chunk.append(originalQueryStringEncoded);
}
if (!chunk.isNull()) {
request.getCoyoteRequest().queryString().toChars();
}
}
// Set the new host if it changed
if (!host.equals(request.getServerName())) {
request.getCoyoteRequest().serverName().setString(null);
chunk = request.getCoyoteRequest().serverName().getCharChunk();
chunk.recycle();
chunk.append(host.toString());
request.getCoyoteRequest().serverName().toChars();
}
request.getMappingData().recycle();
// Reinvoke the whole request recursively
Connector connector = request.getConnector();
try {
if (!connector.getProtocolHandler().getAdapter().prepare(
request.getCoyoteRequest(), response.getCoyoteResponse())) {
return;
}
} catch (Exception e) {
// This doesn't actually happen in the Catalina adapter implementation
}
Pipeline pipeline = connector.getService().getContainer().getPipeline();
request.setAsyncSupported(pipeline.isAsyncSupported());
pipeline.getFirst().invoke(request, response);
}
} else {
getNext().invoke(request, response);
}
} finally {
invoked.set(null);
}
}
/**
* @return config base.
*/
protected File getConfigBase() {
File configBase =
new File(System.getProperty("catalina.base"), "conf");
if (!configBase.exists()) {
return null;
} else {
return configBase;
}
}
/**
* Find the configuration path where the rewrite configuration file
* will be stored.
* @param resourceName The rewrite configuration file name
* @return the full rewrite configuration path
*/
protected String getHostConfigPath(String resourceName) {
StringBuffer result = new StringBuffer();
Container container = getContainer();
Container host = null;
Container engine = null;
while (container != null) {
if (container instanceof Host)
host = container;
if (container instanceof Engine)
engine = container;
container = container.getParent();
}
if (engine != null) {
result.append(engine.getName()).append('/');
}
if (host != null) {
result.append(host.getName()).append('/');
}
result.append(resourceName);
return result.toString();
}
/**
* This factory method will parse a line formed like:
*
* Example:
* RewriteCond %{REMOTE_HOST} ^host1.* [OR]
*
* @param line A line from the rewrite configuration
* @return The condition, rule or map resulting from parsing the line
*/
public static Object parse(String line) {
StringTokenizer tokenizer = new StringTokenizer(line);
if (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.equals("RewriteCond")) {
// RewriteCond TestString CondPattern [Flags]
RewriteCond condition = new RewriteCond();
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
condition.setTestString(tokenizer.nextToken());
condition.setCondPattern(tokenizer.nextToken());
if (tokenizer.hasMoreTokens()) {
String flags = tokenizer.nextToken();
condition.setFlagsString(flags);
if (flags.startsWith("[") && flags.endsWith("]")) {
flags = flags.substring(1, flags.length() - 1);
}
StringTokenizer flagsTokenizer = new StringTokenizer(flags, ",");
while (flagsTokenizer.hasMoreElements()) {
parseCondFlag(line, condition, flagsTokenizer.nextToken());
}
}
return condition;
} else if (token.equals("RewriteRule")) {
// RewriteRule Pattern Substitution [Flags]
RewriteRule rule = new RewriteRule();
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
rule.setPatternString(tokenizer.nextToken());
rule.setSubstitutionString(tokenizer.nextToken());
if (tokenizer.hasMoreTokens()) {
String flags = tokenizer.nextToken();
rule.setFlagsString(flags);
if (flags.startsWith("[") && flags.endsWith("]")) {
flags = flags.substring(1, flags.length() - 1);
}
StringTokenizer flagsTokenizer = new StringTokenizer(flags, ",");
while (flagsTokenizer.hasMoreElements()) {
parseRuleFlag(line, rule, flagsTokenizer.nextToken());
}
}
return rule;
} else if (token.equals("RewriteMap")) {
// RewriteMap name rewriteMapClassName whateverOptionalParameterInWhateverFormat
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
String name = tokenizer.nextToken();
String rewriteMapClassName = tokenizer.nextToken();
RewriteMap map = null;
try {
map = (RewriteMap) (Class.forName(
rewriteMapClassName).getConstructor().newInstance());
} catch (Exception e) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidMapClassName", line));
}
if (tokenizer.hasMoreTokens()) {
map.setParameters(tokenizer.nextToken());
}
Object[] result = new Object[2];
result[0] = name;
result[1] = map;
return result;
} else if (token.startsWith("#")) {
// it's a comment, ignore it
} else {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
}
return null;
}
/**
* Parser for RewriteCond flags.
* @param line The configuration line being parsed
* @param condition The current condition
* @param flag The flag
*/
protected static void parseCondFlag(String line, RewriteCond condition, String flag) {
if (flag.equals("NC") || flag.equals("nocase")) {
condition.setNocase(true);
} else if (flag.equals("OR") || flag.equals("ornext")) {
condition.setOrnext(true);
} else {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
}
/**
* Parser for RewriteRule flags.
* @param line The configuration line being parsed
* @param rule The current rule
* @param flag The flag
*/
protected static void parseRuleFlag(String line, RewriteRule rule, String flag) {
if (flag.equals("B")) {
rule.setEscapeBackReferences(true);
} else if (flag.equals("chain") || flag.equals("C")) {
rule.setChain(true);
} else if (flag.startsWith("cookie=") || flag.startsWith("CO=")) {
rule.setCookie(true);
if (flag.startsWith("cookie")) {
flag = flag.substring("cookie=".length());
} else if (flag.startsWith("CO=")) {
flag = flag.substring("CO=".length());
}
StringTokenizer tokenizer = new StringTokenizer(flag, ":");
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
rule.setCookieName(tokenizer.nextToken());
rule.setCookieValue(tokenizer.nextToken());
if (tokenizer.hasMoreTokens()) {
rule.setCookieDomain(tokenizer.nextToken());
}
if (tokenizer.hasMoreTokens()) {
try {
rule.setCookieLifetime(Integer.parseInt(tokenizer.nextToken()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag), e);
}
}
if (tokenizer.hasMoreTokens()) {
rule.setCookiePath(tokenizer.nextToken());
}
if (tokenizer.hasMoreTokens()) {
rule.setCookieSecure(Boolean.parseBoolean(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
rule.setCookieHttpOnly(Boolean.parseBoolean(tokenizer.nextToken()));
}
} else if (flag.startsWith("env=") || flag.startsWith("E=")) {
rule.setEnv(true);
if (flag.startsWith("env=")) {
flag = flag.substring("env=".length());
} else if (flag.startsWith("E=")) {
flag = flag.substring("E=".length());
}
int pos = flag.indexOf(':');
if (pos == -1 || (pos + 1) == flag.length()) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
rule.addEnvName(flag.substring(0, pos));
rule.addEnvValue(flag.substring(pos + 1));
} else if (flag.startsWith("forbidden") || flag.startsWith("F")) {
rule.setForbidden(true);
} else if (flag.startsWith("gone") || flag.startsWith("G")) {
rule.setGone(true);
} else if (flag.startsWith("host") || flag.startsWith("H")) {
rule.setHost(true);
} else if (flag.startsWith("last") || flag.startsWith("L")) {
rule.setLast(true);
} else if (flag.startsWith("nocase") || flag.startsWith("NC")) {
rule.setNocase(true);
} else if (flag.startsWith("noescape") || flag.startsWith("NE")) {
rule.setNoescape(true);
} else if (flag.startsWith("next") || flag.startsWith("N")) {
rule.setNext(true);
// Note: Proxy is not supported as Tomcat does not have proxy
// capabilities
} else if (flag.startsWith("qsappend") || flag.startsWith("QSA")) {
rule.setQsappend(true);
} else if (flag.startsWith("qsdiscard") || flag.startsWith("QSD")) {
rule.setQsdiscard(true);
} else if (flag.startsWith("redirect") || flag.startsWith("R")) {
rule.setRedirect(true);
int redirectCode = HttpServletResponse.SC_FOUND;
if (flag.startsWith("redirect=") || flag.startsWith("R=")) {
if (flag.startsWith("redirect=")) {
flag = flag.substring("redirect=".length());
} else if (flag.startsWith("R=")) {
flag = flag.substring("R=".length());
}
switch(flag) {
case "temp":
redirectCode = HttpServletResponse.SC_FOUND;
break;
case "permanent":
redirectCode = HttpServletResponse.SC_MOVED_PERMANENTLY;
break;
case "seeother":
redirectCode = HttpServletResponse.SC_SEE_OTHER;
break;
default:
redirectCode = Integer.parseInt(flag);
break;
}
}
rule.setRedirectCode(redirectCode);
} else if (flag.startsWith("skip") || flag.startsWith("S")) {
if (flag.startsWith("skip=")) {
flag = flag.substring("skip=".length());
} else if (flag.startsWith("S=")) {
flag = flag.substring("S=".length());
}
rule.setSkip(Integer.parseInt(flag));
} else if (flag.startsWith("type") || flag.startsWith("T")) {
if (flag.startsWith("type=")) {
flag = flag.substring("type=".length());
} else if (flag.startsWith("T=")) {
flag = flag.substring("T=".length());
}
rule.setType(true);
rule.setTypeValue(flag);
} else {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
}
}

View File

@@ -0,0 +1,347 @@
/*
* 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.catalina.valves.rewrite;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import org.apache.catalina.util.URLEncoder;
public class Substitution {
public abstract class SubstitutionElement {
public abstract String evaluate(Matcher rule, Matcher cond, Resolver resolver);
}
public class StaticElement extends SubstitutionElement {
public String value;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return value;
}
}
public class RewriteRuleBackReferenceElement extends SubstitutionElement {
public int n;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String result = rule.group(n);
if (result == null) {
result = "";
}
if (escapeBackReferences) {
// Note: This should be consistent with the way httpd behaves.
// We might want to consider providing a dedicated decoder
// with an option to add additional safe characters to
// provide users with more flexibility
return URLEncoder.DEFAULT.encode(result, resolver.getUriCharset());
} else {
return result;
}
}
}
public class RewriteCondBackReferenceElement extends SubstitutionElement {
public int n;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return (cond.group(n) == null ? "" : cond.group(n));
}
}
public class ServerVariableElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolve(key);
}
}
public class ServerVariableEnvElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveEnv(key);
}
}
public class ServerVariableSslElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveSsl(key);
}
}
public class ServerVariableHttpElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveHttp(key);
}
}
public class MapElement extends SubstitutionElement {
public RewriteMap map = null;
public SubstitutionElement[] defaultValue = null;
public SubstitutionElement[] key = null;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String result = map.lookup(evaluateSubstitution(key, rule, cond, resolver));
if (result == null && defaultValue != null) {
result = evaluateSubstitution(defaultValue, rule, cond, resolver);
}
return result;
}
}
protected SubstitutionElement[] elements = null;
protected String sub = null;
public String getSub() { return sub; }
public void setSub(String sub) { this.sub = sub; }
private boolean escapeBackReferences;
void setEscapeBackReferences(boolean escapeBackReferences) {
this.escapeBackReferences = escapeBackReferences;
}
public void parse(Map<String, RewriteMap> maps) {
this.elements = parseSubtitution(sub, maps);
}
private SubstitutionElement[] parseSubtitution(String sub, Map<String, RewriteMap> maps) {
List<SubstitutionElement> elements = new ArrayList<>();
int pos = 0;
int percentPos = 0;
int dollarPos = 0;
int backslashPos = 0;
while (pos < sub.length()) {
percentPos = sub.indexOf('%', pos);
dollarPos = sub.indexOf('$', pos);
backslashPos = sub.indexOf('\\', pos);
if (percentPos == -1 && dollarPos == -1 && backslashPos == -1) {
// Static text
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, sub.length());
pos = sub.length();
elements.add(newElement);
} else if (isFirstPos(backslashPos, dollarPos, percentPos)) {
if (backslashPos + 1 == sub.length()) {
throw new IllegalArgumentException(sub);
}
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, backslashPos) + sub.substring(backslashPos + 1, backslashPos + 2);
pos = backslashPos + 2;
elements.add(newElement);
} else if (isFirstPos(dollarPos, percentPos)) {
// $: back reference to rule or map lookup
if (dollarPos + 1 == sub.length()) {
throw new IllegalArgumentException(sub);
}
if (pos < dollarPos) {
// Static text
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, dollarPos);
pos = dollarPos;
elements.add(newElement);
}
if (Character.isDigit(sub.charAt(dollarPos + 1))) {
// $: back reference to rule
RewriteRuleBackReferenceElement newElement = new RewriteRuleBackReferenceElement();
newElement.n = Character.digit(sub.charAt(dollarPos + 1), 10);
pos = dollarPos + 2;
elements.add(newElement);
} else if (sub.charAt(dollarPos + 1) == '{') {
// $: map lookup as ${mapname:key|default}
MapElement newElement = new MapElement();
int open = sub.indexOf('{', dollarPos);
int colon = findMatchingColonOrBar(true, sub, open);
int def = findMatchingColonOrBar(false, sub, open);
int close = findMatchingBrace(sub, open);
if (!(-1 < open && open < colon && colon < close)) {
throw new IllegalArgumentException(sub);
}
newElement.map = maps.get(sub.substring(open + 1, colon));
if (newElement.map == null) {
throw new IllegalArgumentException(sub + ": No map: " + sub.substring(open + 1, colon));
}
String key = null;
String defaultValue = null;
if (def > -1) {
if (!(colon < def && def < close)) {
throw new IllegalArgumentException(sub);
}
key = sub.substring(colon + 1, def);
defaultValue = sub.substring(def + 1, close);
} else {
key = sub.substring(colon + 1, close);
}
newElement.key = parseSubtitution(key, maps);
if (defaultValue != null) {
newElement.defaultValue = parseSubtitution(defaultValue, maps);
}
pos = close + 1;
elements.add(newElement);
} else {
throw new IllegalArgumentException(sub + ": missing digit or curly brace.");
}
} else {
// %: back reference to condition or server variable
if (percentPos + 1 == sub.length()) {
throw new IllegalArgumentException(sub);
}
if (pos < percentPos) {
// Static text
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, percentPos);
pos = percentPos;
elements.add(newElement);
}
if (Character.isDigit(sub.charAt(percentPos + 1))) {
// %: back reference to condition
RewriteCondBackReferenceElement newElement = new RewriteCondBackReferenceElement();
newElement.n = Character.digit(sub.charAt(percentPos + 1), 10);
pos = percentPos + 2;
elements.add(newElement);
} else if (sub.charAt(percentPos + 1) == '{') {
// %: server variable as %{variable}
SubstitutionElement newElement = null;
int open = sub.indexOf('{', percentPos);
int colon = findMatchingColonOrBar(true, sub, open);
int close = findMatchingBrace(sub, open);
if (!(-1 < open && open < close)) {
throw new IllegalArgumentException(sub);
}
if (colon > -1 && open < colon && colon < close) {
String type = sub.substring(open + 1, colon);
if (type.equals("ENV")) {
newElement = new ServerVariableEnvElement();
((ServerVariableEnvElement) newElement).key = sub.substring(colon + 1, close);
} else if (type.equals("SSL")) {
newElement = new ServerVariableSslElement();
((ServerVariableSslElement) newElement).key = sub.substring(colon + 1, close);
} else if (type.equals("HTTP")) {
newElement = new ServerVariableHttpElement();
((ServerVariableHttpElement) newElement).key = sub.substring(colon + 1, close);
} else {
throw new IllegalArgumentException(sub + ": Bad type: " + type);
}
} else {
newElement = new ServerVariableElement();
((ServerVariableElement) newElement).key = sub.substring(open + 1, close);
}
pos = close + 1;
elements.add(newElement);
} else {
throw new IllegalArgumentException(sub + ": missing digit or curly brace.");
}
}
}
return elements.toArray(new SubstitutionElement[0]);
}
private static int findMatchingBrace(String sub, int start) {
int nesting = 1;
for (int i = start + 1; i < sub.length(); i++) {
char c = sub.charAt(i);
if (c == '{') {
char previousChar = sub.charAt(i-1);
if (previousChar == '$' || previousChar == '%') {
nesting++;
}
} else if (c == '}') {
nesting--;
if (nesting == 0) {
return i;
}
}
}
return -1;
}
private static int findMatchingColonOrBar(boolean colon, String sub, int start) {
int nesting = 0;
for (int i = start + 1; i < sub.length(); i++) {
char c = sub.charAt(i);
if (c == '{') {
char previousChar = sub.charAt(i-1);
if (previousChar == '$' || previousChar == '%') {
nesting++;
}
} else if (c == '}') {
nesting--;
} else if (colon ? c == ':' : c =='|') {
if (nesting == 0) {
return i;
}
}
}
return -1;
}
/**
* Evaluate the substitution based on the context.
* @param rule corresponding matched rule
* @param cond last matched condition
* @param resolver The property resolver
* @return The substitution result
*/
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return evaluateSubstitution(elements, rule, cond, resolver);
}
private String evaluateSubstitution(SubstitutionElement[] elements, Matcher rule, Matcher cond, Resolver resolver) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < elements.length; i++) {
buf.append(elements[i].evaluate(rule, cond, resolver));
}
return buf.toString();
}
/**
* Checks whether the first int is non negative and smaller than any non negative other int
* given with {@code others}.
*
* @param testPos
* integer to test against
* @param others
* list of integers that are paired against {@code testPos}. Any
* negative integer will be ignored.
* @return {@code true} if {@code testPos} is not negative and is less then any given other
* integer, {@code false} otherwise
*/
private boolean isFirstPos(int testPos, int... others) {
if (testPos < 0) {
return false;
}
for (int other : others) {
if (other >= 0 && other < testPos) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<mbeans-descriptors>
<mbean name="RewriteValve"
description="A URL rewrite valve"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.rewrite.RewriteValve">
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="configuration"
description="Rewrite configuration"
type="java.lang.String" />
</mbean>
</mbeans-descriptors>