/* * 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.connector;
/** * Implementation of a request processor which delegates the processing to a Coyote processor. * * @author Craig R. McClanahan * @author Remy Maucherat
*/ publicclass CoyoteAdapter implements Adapter {
/** * Construct a new CoyoteProcessor associated with the specified connector. * * @param connector CoyoteConnector that owns this processor
*/ public CoyoteAdapter(Connector connector) {
try { if (!request.isAsync()) { // Error or timeout // Lift any suspension (e.g. if sendError() was used by an async // request) to allow the response to be written to the client
response.setSuspended(false);
}
if (status == SocketEvent.TIMEOUT) { if (!asyncConImpl.timeout()) {
asyncConImpl.setErrorState(null, false);
}
} elseif (status == SocketEvent.ERROR) { // An I/O error occurred on a non-container thread which means // that the socket needs to be closed so set success to false to // trigger a close
success = false;
Throwable t = (Throwable) req.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
Context context = request.getContext();
ClassLoader oldCL = null; try {
oldCL = context.bind(false, null); if (req.getReadListener() != null) {
req.getReadListener().onError(t);
} if (res.getWriteListener() != null) {
res.getWriteListener().onError(t);
}
res.action(ActionCode.CLOSE_NOW, t);
asyncConImpl.setErrorState(t, true);
} finally {
context.unbind(false, oldCL);
}
}
// Check to see if non-blocking writes or reads are being used if (!request.isAsyncDispatching() && request.isAsync()) {
WriteListener writeListener = res.getWriteListener();
ReadListener readListener = req.getReadListener(); if (writeListener != null && status == SocketEvent.OPEN_WRITE) {
Context context = request.getContext();
ClassLoader oldCL = null; try {
oldCL = context.bind(false, null);
res.onWritePossible(); if (request.isFinished() && req.sendAllDataReadEvent() && readListener != null) {
readListener.onAllDataRead();
} // User code may have swallowed an IOException if (response.getCoyoteResponse().isExceptionPresent()) { throw response.getCoyoteResponse().getErrorException();
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t); // Need to trigger the call to AbstractProcessor.setErrorState() // before the listener is called so the listener can call complete // Therefore no need to set success=false as that would trigger a // second call to AbstractProcessor.setErrorState() // https://bz.apache.org/bugzilla/show_bug.cgi?id=65001
writeListener.onError(t);
res.action(ActionCode.CLOSE_NOW, t);
asyncConImpl.setErrorState(t, true);
} finally {
context.unbind(false, oldCL);
}
} elseif (readListener != null && status == SocketEvent.OPEN_READ) {
Context context = request.getContext();
ClassLoader oldCL = null; try {
oldCL = context.bind(false, null); // If data is being read on a non-container thread a // dispatch with status OPEN_READ will be used to get // execution back on a container thread for the // onAllDataRead() event. Therefore, make sure // onDataAvailable() is not called in this case. if (!request.isFinished()) {
req.onDataAvailable();
} if (request.isFinished() && req.sendAllDataReadEvent()) {
readListener.onAllDataRead();
} // User code may have swallowed an IOException if (request.getCoyoteRequest().isExceptionPresent()) { throw request.getCoyoteRequest().getErrorException();
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t); // Need to trigger the call to AbstractProcessor.setErrorState() // before the listener is called so the listener can call complete // Therefore no need to set success=false as that would trigger a // second call to AbstractProcessor.setErrorState() // https://bz.apache.org/bugzilla/show_bug.cgi?id=65001
readListener.onError(t);
res.action(ActionCode.CLOSE_NOW, t);
asyncConImpl.setErrorState(t, true);
} finally {
context.unbind(false, oldCL);
}
}
}
// Has an error occurred during async processing that needs to be // processed by the application's error page mechanism (or Tomcat's // if the application doesn't define one)? if (!request.isAsyncDispatching() && request.isAsync() && response.isErrorReportRequired()) {
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
}
if (request.isAsyncDispatching()) {
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); if (response.isError()) {
Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
asyncConImpl.setErrorState(t, true);
}
}
if (!request.isAsync()) {
request.finishRequest();
response.finishResponse();
}
// Check to see if the processor is in an error state. If it is, // bail out now.
AtomicBoolean error = new AtomicBoolean(false);
res.action(ActionCode.IS_ERROR, error); if (error.get()) { if (request.isAsyncCompleting() || request.isAsyncDispatching()) { // Connection will be forcibly closed which will prevent completion/dispatch happening at the usual // point. Trigger post processing here.
res.action(ActionCode.ASYNC_POST_PROCESS, null);
}
success = false;
}
} catch (IOException e) {
success = false; // Ignore
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
success = false;
log.error(sm.getString("coyoteAdapter.asyncDispatch"), t);
} finally { if (!success) {
res.setStatus(500);
}
// Access logging if (!success || !request.isAsync()) { long time = 0; if (req.getStartTimeNanos() != -1) {
time = System.nanoTime() - req.getStartTimeNanos();
}
Context context = request.getContext(); if (context != null) {
context.logAccess(request, response, time, false);
} else {
log(req, res, time);
}
}
req.getRequestProcessor().setWorkerThreadName(null);
req.clearRequestThread(); // Recycle the wrapper request and response if (!success || !request.isAsync()) {
updateWrapperErrorCount(request, response);
request.recycle();
response.recycle();
}
} return success;
}
try { // Parse and set Catalina and configuration specific // request parameters
postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { // check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
} if (request.isAsync()) {
async = true;
ReadListener readListener = req.getReadListener(); if (readListener != null && request.isFinished()) { // Possible the all data may have been read during service() // method so this needs to be checked here
ClassLoader oldCL = null; try {
oldCL = request.getContext().bind(false, null); if (req.sendAllDataReadEvent()) {
req.getReadListener().onAllDataRead();
}
} finally {
request.getContext().unbind(false, oldCL);
}
}
// If an async request was started, is not going to end once // this container thread finishes and an error occurred, trigger // the async error process if (!request.isAsyncCompleting() && throwable != null) {
request.getAsyncContextInternal().setErrorState(throwable, true);
}
} else {
request.finishRequest();
response.finishResponse();
}
if (request.isAsyncCompleting() && error.get()) { // Connection will be forcibly closed which will prevent // completion happening at the usual point. Need to trigger // call to onComplete() here.
res.action(ActionCode.ASYNC_POST_PROCESS, null);
async = false;
}
// Access log if (!async && postParseSuccess) { // Log only if processing was invoked. // If postParseRequest() failed, it has already logged it.
Context context = request.getContext();
Host host = request.getHost(); // If the context is null, it is likely that the endpoint was // shutdown, this connection closed and the request recycled in // a different thread. That thread will have updated the access // log so it is OK not to update the access log here in that // case. // The other possibility is that an error occurred early in // processing and the request could not be mapped to a Context. // Log via the host or engine in that case. long time = System.nanoTime() - req.getStartTimeNanos(); if (context != null) {
context.logAccess(request, response, time, false);
} elseif (response.isError()) { if (host != null) {
host.logAccess(request, response, time, false);
} else {
connector.getService().getContainer().logAccess(request, response, time, false);
}
}
}
@Override publicvoid checkRecycled(org.apache.coyote.Request req, org.apache.coyote.Response res) {
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
String messageKey = null; if (request != null && request.getHost() != null) {
messageKey = "coyoteAdapter.checkRecycled.request";
} elseif (response != null && response.getContentWritten() != 0) {
messageKey = "coyoteAdapter.checkRecycled.response";
} if (messageKey != null) { // Log this request, as it has probably skipped the access log. // The log() method will take care of recycling.
log(req, res, 0L);
if (connector.getState().isAvailable()) { if (log.isInfoEnabled()) {
log.info(sm.getString(messageKey), new RecycleRequiredException());
}
} else { // There may be some aborted requests. // When connector shuts down, the request and response will not // be reused, so there is no issue to warn about here. if (log.isDebugEnabled()) {
log.debug(sm.getString(messageKey), new RecycleRequiredException());
}
}
}
}
@Override public String getDomain() { return connector.getDomain();
}
/** * Perform the necessary processing after the HTTP headers have been parsed to enable the request/response pair to * be passed to the start of the container pipeline for processing. * * @param req The coyote request object * @param request The catalina request object * @param res The coyote response object * @param response The catalina response object * * @return <code>true</code> if the request should be passed on to the start of the container pipeline, otherwise * <code>false</code> * * @throws IOException If there is insufficient space in a buffer while processing headers * @throws ServletException If the supported methods of the target servlet cannot be determined
*/
@SuppressWarnings("deprecation") protectedboolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res,
Response response) throws IOException, ServletException {
// If the processor has set the scheme (AJP does this, HTTP does this if // SSL is enabled) use this to set the secure flag as well. If the // processor hasn't set it, use the settings from the connector if (req.scheme().isNull()) { // Use connector scheme and secure configuration, (defaults to // "http" and false respectively)
req.scheme().setString(connector.getScheme());
request.setSecure(connector.getSecure());
} else { // Use processor specified scheme to determine secure state
request.setSecure(req.scheme().equals("https"));
}
// At this point the Host header has been processed. // Override if the proxyPort/proxyHost are set
String proxyName = connector.getProxyName(); int proxyPort = connector.getProxyPort(); if (proxyPort != 0) {
req.setServerPort(proxyPort);
} elseif (req.getServerPort() == -1) { // Not explicitly set. Use default ports based on the scheme if (req.scheme().equals("https")) {
req.setServerPort(443);
} else {
req.setServerPort(80);
}
} if (proxyName != null) {
req.serverName().setString(proxyName);
}
MessageBytes undecodedURI = req.requestURI();
// Check for ping OPTIONS * request if (undecodedURI.equals("*")) { if (req.method().equals("OPTIONS")) {
StringBuilder allow = new StringBuilder();
allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS"); // Trace if allowed if (connector.getAllowTrace()) {
allow.append(", TRACE");
}
res.setHeader("Allow", allow.toString()); // Access log entry as processing won't reach AccessLogValve
connector.getService().getContainer().logAccess(request, response, 0, true); returnfalse;
} else {
response.sendError(400, sm.getString("coyoteAdapter.invalidURI"));
}
}
MessageBytes decodedURI = req.decodedURI();
// Filter CONNECT method if (req.method().equals("CONNECT")) {
response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, sm.getString("coyoteAdapter.connect"));
} else { // No URI for CONNECT requests if (undecodedURI.getType() == MessageBytes.T_BYTES) { if (connector.getRejectSuspiciousURIs()) { if (checkSuspiciousURIs(undecodedURI.getByteChunk())) {
response.sendError(400, sm.getString("coyoteAdapter.invalidURI"));
}
}
// Copy the raw URI to the decodedURI
decodedURI.duplicate(undecodedURI);
// Parse (and strip out) the path parameters
parsePathParameters(req, request);
// URI decoding // %xx decoding of the URL try {
req.getURLDecoder().convert(decodedURI.getByteChunk(),
connector.getEncodedSolidusHandlingInternal());
} catch (IOException ioe) {
response.sendError(400, sm.getString("coyoteAdapter.invalidURIWithMessage", ioe.getMessage()));
} // Normalization if (normalize(req.decodedURI(), connector.getAllowBackslash())) { // Character decoding
convertURI(decodedURI, request); // URIEncoding values are limited to US-ASCII supersets. // Therefore it is not necessary to check that the URI remains // normalized after character decoding
} else {
response.sendError(400, sm.getString("coyoteAdapter.invalidURI"));
}
} else { /* * The URI is chars or String, and has been sent using an in-memory protocol handler. The following * assumptions are made: - req.requestURI() has been set to the 'original' non-decoded, non-normalized * URI - req.decodedURI() has been set to the decoded, normalized form of req.requestURI() - * 'suspicious' URI filtering - if required - has already been performed
*/
decodedURI.toChars(); // Remove all path parameters; any needed path parameter should be set // using the request object rather than passing it in the URL
CharChunk uriCC = decodedURI.getCharChunk(); int semicolon = uriCC.indexOf(';'); if (semicolon > 0) {
decodedURI.setChars(uriCC.getBuffer(), uriCC.getStart(), semicolon);
}
}
}
// Request mapping.
MessageBytes serverName; if (connector.getUseIPVHosts()) {
serverName = req.localName(); if (serverName.isNull()) { // well, they did ask for it
res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
}
} else {
serverName = req.serverName();
}
// Version for the second mapping loop and // Context that we expect to get for that version
String version = null;
Context versionContext = null; boolean mapRequired = true;
if (response.isError()) { // An error this early means the URI is invalid. Ensure invalid data // is not passed to the mapper. Note we still want the mapper to // find the correct host.
decodedURI.recycle();
}
while (mapRequired) { // This will map the the latest version by default
connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());
// If there is no context at this point, either this is a 404 // because no ROOT context has been deployed or the URI was invalid // so no context could be mapped. if (request.getContext() == null) { // Allow processing to continue. // If present, the rewrite Valve may rewrite this to a valid // request. // The StandardEngineValve will handle the case of a missing // Host and the StandardHostValve the case of a missing Context. // If present, the error reporting valve will provide a response // body. returntrue;
}
// Now we have the context, we can parse the session ID from the URL // (if any). Need to do this before we redirect in case we need to // include the session id in the redirect
String sessionID; if (request.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {
// Get the session ID if there was one
sessionID = request.getPathParameter(SessionConfig.getSessionUriParamName(request.getContext())); if (sessionID != null) {
request.setRequestedSessionId(sessionID);
request.setRequestedSessionURL(true);
}
}
// Look for session ID in cookies and SSL session try {
parseSessionCookiesId(request);
} catch (IllegalArgumentException e) { // Too many cookies if (!response.isError()) {
response.setError();
response.sendError(400, e.getMessage());
} returntrue;
}
parseSessionSslId(request);
sessionID = request.getRequestedSessionId();
mapRequired = false; if (version != null && request.getContext() == versionContext) { // We got the version that we asked for. That is it.
} else {
version = null;
versionContext = null;
Context[] contexts = request.getMappingData().contexts; // Single contextVersion means no need to remap // No session ID means no possibility of remap if (contexts != null && sessionID != null) { // Find the context associated with the session for (int i = contexts.length; i > 0; i--) {
Context ctxt = contexts[i - 1]; if (ctxt.getManager().findSession(sessionID) != null) { // We found a context. Is it the one that has // already been mapped? if (!ctxt.equals(request.getMappingData().context)) { // Set version so second time through mapping // the correct context is found
version = ctxt.getWebappVersion();
versionContext = ctxt; // Reset mapping
request.getMappingData().recycle();
mapRequired = true; // Recycle cookies and session info in case the // correct context is configured with different // settings
request.recycleSessionInfo();
request.recycleCookieInfo(true);
} break;
}
}
}
}
if (!mapRequired && request.getContext().getPaused()) { // Found a matching context but it is paused. Mapping data will // be wrong since some Wrappers may not be registered at this // point. try { Thread.sleep(1000);
} catch (InterruptedException e) { // Should never happen
} // Reset mapping
request.getMappingData().recycle();
mapRequired = true;
}
}
// Possible redirect
MessageBytes redirectPathMB = request.getMappingData().redirectPath; if (!redirectPathMB.isNull()) {
String redirectPath = URLEncoder.DEFAULT.encode(redirectPathMB.toString(), StandardCharsets.UTF_8);
String query = request.getQueryString(); if (request.isRequestedSessionIdFromURL()) { // This is not optimal, but as this is not very common, it // shouldn't matter
redirectPath = redirectPath + ";" + SessionConfig.getSessionUriParamName(request.getContext()) + "=" +
request.getRequestedSessionId();
} if (query != null) { // This is not optimal, but as this is not very common, it // shouldn't matter
redirectPath = redirectPath + "?" + query;
}
response.sendRedirect(redirectPath);
request.getContext().logAccess(request, response, 0, true); returnfalse;
}
// Filter TRACE method if (!connector.getAllowTrace() && req.method().equals("TRACE")) {
Wrapper wrapper = request.getWrapper();
String header = null; if (wrapper != null) {
String[] methods = wrapper.getServletMethods(); if (methods != null) { for (String method : methods) { if ("TRACE".equals(method)) { continue;
} if (header == null) {
header = method;
} else {
header += ", " + method;
}
}
}
} if (header != null) {
res.addHeader("Allow", header);
}
response.sendError(405, sm.getString("coyoteAdapter.trace")); // Safe to skip the remainder of this method. returntrue;
}
privatevoid doConnectorAuthenticationAuthorization(org.apache.coyote.Request req, Request request) { // Set the remote principal
String username = req.getRemoteUser().toString(); if (username != null) { if (log.isDebugEnabled()) {
log.debug(sm.getString("coyoteAdapter.authenticate", username));
} if (req.getRemoteUserNeedsAuthorization()) {
Authenticator authenticator = request.getContext().getAuthenticator(); if (!(authenticator instanceof AuthenticatorBase)) { if (log.isDebugEnabled()) {
log.debug(sm.getString("coyoteAdapter.authorize", username));
} // Custom authenticator that may not trigger authorization. // Do the authorization here to make sure it is done.
request.setUserPrincipal(request.getContext().getRealm().authenticate(username));
} // If the Authenticator is an instance of AuthenticatorBase then // it will check req.getRemoteUserNeedsAuthorization() and // trigger authorization as necessary. It will also cache the // result preventing excessive calls to the Realm.
} else { // The connector isn't configured for authorization. Create a // user without any roles using the supplied user name.
request.setUserPrincipal(new CoyotePrincipal(username));
}
}
// Set the authorization type
String authType = req.getAuthType().toString(); if (authType != null) {
request.setAuthType(authType);
}
}
/** * Extract the path parameters from the request. This assumes parameters are of the form * /path;name=value;name2=value2/ etc. Currently only really interested in the session ID that will be in this form. * Other parameters can safely be ignored. * * @param req The Coyote request object * @param request The Servlet request object
*/ protectedvoid parsePathParameters(org.apache.coyote.Request req, Request request) {
// Process in bytes (this is default format so this is normally a NO-OP
req.decodedURI().toBytes();
ByteChunk uriBC = req.decodedURI().getByteChunk(); // The first character must always be '/' so start search at position 1. // If the first character is ';' the URI will be rejected at the // normalization stage int semicolon = uriBC.indexOf(';', 1); // Performance optimisation. Return as soon as it is known there are no // path parameters; if (semicolon == -1) { return;
}
// What encoding to use? Some platforms, eg z/os, use a default // encoding that doesn't give the expected result so be explicit
Charset charset = connector.getURICharset();
if (pv != null) { int equals = pv.indexOf('='); if (equals > -1) {
String name = pv.substring(0, equals);
String value = pv.substring(equals + 1);
request.addPathParameter(name, value); if (log.isDebugEnabled()) {
log.debug(sm.getString("coyoteAdapter.debug", "equals", String.valueOf(equals)));
log.debug(sm.getString("coyoteAdapter.debug", "name", name));
log.debug(sm.getString("coyoteAdapter.debug", "value", value));
}
}
}
semicolon = uriBC.indexOf(';', semicolon);
}
}
/** * Look for SSL session ID if required. Only look for SSL Session ID if it is the only tracking method enabled. * * @param request The Servlet request object
*/ protectedvoid parseSessionSslId(Request request) { if (request.getRequestedSessionId() == null &&
SSL_ONLY.equals(request.getServletContext().getEffectiveSessionTrackingModes()) &&
request.connector.secure) {
String sessionId = (String) request.getAttribute(SSLSupport.SESSION_ID_KEY); if (sessionId != null) {
request.setRequestedSessionId(sessionId);
request.setRequestedSessionSSL(true);
}
}
}
/** * Parse session id in Cookie. * * @param request The Servlet request object
*/ protectedvoid parseSessionCookiesId(Request request) {
// If session tracking via cookies has been disabled for the current // context, don't go looking for a session ID in a cookie as a cookie // from a parent context with a session ID may be present which would // overwrite the valid session ID encoded in the URL
Context context = request.getMappingData().context; if (context != null &&
!context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE)) { return;
}
// Parse session id from cookies
ServerCookies serverCookies = request.getServerCookies(); int count = serverCookies.getCookieCount(); if (count <= 0) { return;
}
for (int i = 0; i < count; i++) {
ServerCookie scookie = serverCookies.getCookie(i); if (scookie.getName().equals(sessionCookieName)) { // Override anything requested in the URL if (!request.isRequestedSessionIdFromCookie()) { // Accept only the first session id cookie
convertMB(scookie.getValue());
request.setRequestedSessionId(scookie.getValue().toString());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false); if (log.isDebugEnabled()) {
log.debug(" Requested cookie session id is " + request.getRequestedSessionId());
}
} else { if (!request.isRequestedSessionIdValid()) { // Replace the session id until one is valid
convertMB(scookie.getValue());
request.setRequestedSessionId(scookie.getValue().toString());
}
}
}
}
}
/** * Character conversion of the URI. * * @param uri MessageBytes object containing the URI * @param request The Servlet request object * * @throws IOException if a IO exception occurs sending an error to the client
*/ protectedvoid convertURI(MessageBytes uri, Request request) throws IOException {
ByteChunk bc = uri.getByteChunk(); int length = bc.getLength();
CharChunk cc = uri.getCharChunk();
cc.allocate(length, -1);
Charset charset = connector.getURICharset();
B2CConverter conv = request.getURIConverter(); if (conv == null) {
conv = new B2CConverter(charset, false);
request.setURIConverter(conv);
} else {
conv.recycle();
}
try {
conv.convert(bc, cc, true);
uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength());
} catch (IOException ioe) { // Should never happen as B2CConverter should replace // problematic characters
request.getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}
/** * Character conversion of the a US-ASCII MessageBytes. * * @param mb The MessageBytes instance containing the bytes that should be converted to chars
*/ protectedvoid convertMB(MessageBytes mb) {
// This is of course only meaningful for bytes if (mb.getType() != MessageBytes.T_BYTES) { return;
}
ByteChunk bc = mb.getByteChunk();
CharChunk cc = mb.getCharChunk(); int length = bc.getLength();
cc.allocate(length, -1);
// Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) {
cbuf[i] = (char) (bbuf[i + start] & 0xff);
}
mb.setChars(cbuf, 0, length);
}
/** * This method normalizes "\", "//", "/./" and "/../". * * @param uriMB URI to be normalized * @param allowBackslash <code>true</code> if backslash characters are allowed in URLs * * @return <code>false</code> if normalizing this URI would require going above the root, or if the URI contains a * null byte, otherwise <code>true</code>
*/ publicstaticboolean normalize(MessageBytes uriMB, boolean allowBackslash) {
ByteChunk uriBC = uriMB.getByteChunk(); finalbyte[] b = uriBC.getBytes(); finalint start = uriBC.getStart(); int end = uriBC.getEnd(); boolean appendedSlash = false;
// An empty URL is not acceptable if (start == end) { returnfalse;
}
int pos = 0; int index = 0;
// The URL must start with '/' (or '\' that will be replaced soon) if (b[start] != (byte) '/' && b[start] != (byte) '\\') { returnfalse;
}
// Replace '\' with '/' // Check for null byte for (pos = start; pos < end; pos++) { if (b[pos] == (byte) '\\') { if (allowBackslash) {
b[pos] = (byte) '/';
} else { returnfalse;
}
} elseif (b[pos] == (byte) 0) { returnfalse;
}
}
// Replace "//" with "/" for (pos = start; pos < (end - 1); pos++) { if (b[pos] == (byte) '/') { while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
copyBytes(b, pos, pos + 1, end - pos - 1);
end--;
}
}
}
// If the URI ends with "/." or "/..", then we append an extra "/" // Note: It is possible to extend the URI by 1 without any side effect // as the next character is a non-significant WS. if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) { if ((b[end - 2] == (byte) '/') || ((b[end - 2] == (byte) '.') && (b[end - 3] == (byte) '/'))) {
b[end] = (byte) '/';
end++;
appendedSlash = true;
}
}
uriBC.setEnd(end);
index = 0;
// Resolve occurrences of "/./" in the normalized path while (true) {
index = uriBC.indexOf("/./", 0, 3, index); if (index < 0) { break;
}
copyBytes(b, start + index, start + index + 2, end - start - index - 2);
end = end - 2;
uriBC.setEnd(end);
}
index = 0;
// Resolve occurrences of "/../" in the normalized path while (true) {
index = uriBC.indexOf("/../", 0, 4, index); if (index < 0) { break;
} // Prevent from going outside our context if (index == 0) { returnfalse;
} int index2 = -1; for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos--) { if (b[pos] == (byte) '/') {
index2 = pos;
}
}
copyBytes(b, start + index2, start + index + 3, end - start - index - 3);
end = end + index2 - index - 3;
uriBC.setEnd(end);
index = index2;
}
// If a slash was appended to help normalize "/." or "/.." then remove // any trailing "/" from the result unless the result is "/". if (appendedSlash && end > 1 && b[end - 1] == '/') {
uriBC.setEnd(end - 1);
}
returntrue;
}
/** * Copy an array of bytes to a different position. Used during normalization. * * @param b The bytes that should be copied * @param dest Destination offset * @param src Source offset * @param len Length
*/ protectedstaticvoid copyBytes(byte[] b, int dest, int src, int len) {
System.arraycopy(b, src, b, dest, len);
}
/* * Examine URI segment by segment for 'suspicious' URIs.
*/ privatestaticboolean checkSuspiciousURIs(ByteChunk undecodedURI) { byte[] bytes = undecodedURI.getBytes(); int start = undecodedURI.getStart(); int end = undecodedURI.getEnd(); int segmentStart = -1; int segmentEnd = -1;
// Find first segment
segmentStart = undecodedURI.indexOf('/', 0); if (segmentStart > -1) {
segmentEnd = undecodedURI.indexOf('/', segmentStart + 1);
}
while (segmentStart > -1) { int pos = start + segmentStart + 1;
// Empty segment other than final segment with path parameters if (segmentEnd > 0 && bytes[pos] == ';') { returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.