| /* |
| * 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.authenticator; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.security.Principal; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.Locale; |
| |
| import jakarta.servlet.RequestDispatcher; |
| import jakarta.servlet.http.Cookie; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import org.apache.catalina.Realm; |
| import org.apache.catalina.Session; |
| import org.apache.catalina.connector.Request; |
| import org.apache.coyote.ActionCode; |
| import org.apache.coyote.ContinueResponseTiming; |
| import org.apache.juli.logging.Log; |
| import org.apache.juli.logging.LogFactory; |
| import org.apache.tomcat.util.ExceptionUtils; |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.descriptor.web.LoginConfig; |
| import org.apache.tomcat.util.http.MimeHeaders; |
| |
| /** |
| * An <b>Authenticator</b> and <b>Valve</b> implementation of FORM BASED Authentication, as described in the Servlet API |
| * Specification. |
| * |
| * @author Craig R. McClanahan |
| * @author Remy Maucherat |
| */ |
| public class FormAuthenticator extends AuthenticatorBase { |
| |
| private final Log log = LogFactory.getLog(FormAuthenticator.class); // must not be static |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| /** |
| * Character encoding to use to read the username and password parameters from the request. If not set, the encoding |
| * of the request body will be used. |
| */ |
| protected String characterEncoding = null; |
| |
| /** |
| * Landing page to use if a user tries to access the login page directly or if the session times out during login. |
| * If not set, error responses will be sent instead. |
| */ |
| protected String landingPage = null; |
| |
| /** |
| * If the authentication process creates a session, this is the maximum session timeout (in seconds) during the |
| * authentication process. Once authentication is complete, the default session timeout will apply. Sessions that |
| * exist before the authentication process starts will retain their original session timeout throughout. |
| */ |
| protected int authenticationSessionTimeout = 120; |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| /** |
| * Return the character encoding to use to read the user name and password. |
| * |
| * @return The name of the character encoding |
| */ |
| public String getCharacterEncoding() { |
| return characterEncoding; |
| } |
| |
| |
| /** |
| * Set the character encoding to be used to read the user name and password. |
| * |
| * @param encoding The name of the encoding to use |
| */ |
| public void setCharacterEncoding(String encoding) { |
| characterEncoding = encoding; |
| } |
| |
| |
| /** |
| * Return the landing page to use when FORM auth is mis-used. |
| * |
| * @return The path to the landing page relative to the web application root |
| */ |
| public String getLandingPage() { |
| return landingPage; |
| } |
| |
| |
| /** |
| * Set the landing page to use when the FORM auth is mis-used. |
| * |
| * @param landingPage The path to the landing page relative to the web application root |
| */ |
| public void setLandingPage(String landingPage) { |
| this.landingPage = landingPage; |
| } |
| |
| |
| /** |
| * Returns the maximum session timeout to be used during authentication if the authentication process creates a |
| * session. |
| * |
| * @return the maximum session timeout to be used during authentication if the authentication process creates a |
| * session |
| */ |
| public int getAuthenticationSessionTimeout() { |
| return authenticationSessionTimeout; |
| } |
| |
| |
| /** |
| * Configures the maximum session timeout to be used during authentication if the authentication process creates a |
| * session. |
| * |
| * @param authenticationSessionTimeout The maximum session timeout to use duriing authentication if the |
| * authentication process creates a session |
| */ |
| public void setAuthenticationSessionTimeout(int authenticationSessionTimeout) { |
| this.authenticationSessionTimeout = authenticationSessionTimeout; |
| } |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| /** |
| * Authenticate the user making this request, based on the specified login configuration. Return <code>true</code> |
| * if any specified constraint has been satisfied, or <code>false</code> if we have created a response challenge |
| * already. |
| * |
| * @param request Request we are processing |
| * @param response Response we are creating |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| @Override |
| protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException { |
| |
| // References to objects we will need later |
| Session session = null; |
| Principal principal = null; |
| |
| // Have we authenticated this user before but have caching disabled? |
| if (!cache) { |
| session = request.getSessionInternal(true); |
| if (log.isTraceEnabled()) { |
| log.trace("Checking for reauthenticate in session " + session); |
| } |
| String username = (String) session.getNote(Constants.SESS_USERNAME_NOTE); |
| String password = (String) session.getNote(Constants.SESS_PASSWORD_NOTE); |
| if (username != null && password != null) { |
| if (log.isTraceEnabled()) { |
| log.trace("Reauthenticating username '" + username + "'"); |
| } |
| principal = context.getRealm().authenticate(username, password); |
| if (principal != null) { |
| register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password); |
| if (!matchRequest(request)) { |
| return true; |
| } |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("formAuthenticator.reauthFailed")); |
| } |
| } |
| } |
| |
| // Is this the re-submit of the original request URI after successful |
| // authentication? If so, forward the *original* request instead. |
| if (matchRequest(request)) { |
| session = request.getSessionInternal(true); |
| if (log.isTraceEnabled()) { |
| log.trace("Restore request from session '" + session.getIdInternal() + "'"); |
| } |
| if (restoreRequest(request, session)) { |
| if (log.isTraceEnabled()) { |
| log.trace("Proceed to restored request"); |
| } |
| return true; |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("formAuthenticator.restoreFailed")); |
| } |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST); |
| return false; |
| } |
| } |
| |
| // This check has to be after the previous check for a matching request |
| // because that matching request may also include a cached Principal. |
| if (checkForCachedAuthentication(request, response, true)) { |
| return true; |
| } |
| |
| // Acquire references to objects we will need to evaluate |
| String contextPath = request.getContextPath(); |
| String requestURI = request.getDecodedRequestURI(); |
| |
| // Is this the action request from the login page? |
| boolean loginAction = requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION); |
| |
| LoginConfig config = context.getLoginConfig(); |
| |
| // No -- Save this request and redirect to the form login page |
| if (!loginAction) { |
| // If this request was to the root of the context without a trailing |
| // '/', need to redirect to add it else the submit of the login form |
| // may not go to the correct web application |
| if (request.getServletPath().length() == 0 && request.getPathInfo() == null) { |
| StringBuilder location = new StringBuilder(requestURI); |
| location.append('/'); |
| if (request.getQueryString() != null) { |
| location.append('?'); |
| location.append(request.getQueryString()); |
| } |
| response.sendRedirect(response.encodeRedirectURL(location.toString())); |
| return false; |
| } |
| |
| session = request.getSessionInternal(true); |
| if (log.isTraceEnabled()) { |
| log.trace("Save request in session '" + session.getIdInternal() + "'"); |
| } |
| try { |
| saveRequest(request, session); |
| } catch (IOException ioe) { |
| log.debug(sm.getString("authenticator.requestBodyTooBig")); |
| response.sendError(HttpServletResponse.SC_FORBIDDEN, sm.getString("authenticator.requestBodyTooBig")); |
| return false; |
| } |
| forwardToLoginPage(request, response, config); |
| return false; |
| } |
| |
| // Yes -- Acknowledge the request, validate the specified credentials |
| // and redirect to the error page if they are not correct |
| request.getResponse().sendAcknowledgement(ContinueResponseTiming.ALWAYS); |
| Realm realm = context.getRealm(); |
| if (characterEncoding != null) { |
| request.setCharacterEncoding(characterEncoding); |
| } |
| String username = request.getParameter(Constants.FORM_USERNAME); |
| String password = request.getParameter(Constants.FORM_PASSWORD); |
| if (log.isTraceEnabled()) { |
| log.trace("Authenticating username '" + username + "'"); |
| } |
| principal = realm.authenticate(username, password); |
| if (principal == null) { |
| forwardToErrorPage(request, response, config); |
| return false; |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Authentication of '" + username + "' was successful"); |
| } |
| |
| if (session == null) { |
| session = request.getSessionInternal(false); |
| } |
| if (session != null && getChangeSessionIdOnAuthentication()) { |
| // Does session id match? |
| String expectedSessionId = (String) session.getNote(Constants.SESSION_ID_NOTE); |
| if (expectedSessionId == null || !expectedSessionId.equals(request.getRequestedSessionId())) { |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("formAuthenticator.sessionIdMismatch", session.getId(), expectedSessionId)); |
| } |
| session.expire(); |
| session = null; |
| } |
| } |
| if (session == null) { |
| if (containerLog.isDebugEnabled()) { |
| containerLog.debug(sm.getString("formAuthenticator.sessionExpired")); |
| } |
| if (landingPage == null) { |
| response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT, |
| sm.getString("authenticator.sessionExpired")); |
| } else { |
| // Make the authenticator think the user originally requested |
| // the landing page |
| String uri = request.getContextPath() + landingPage; |
| SavedRequest saved = new SavedRequest(); |
| saved.setMethod("GET"); |
| saved.setRequestURI(uri); |
| saved.setDecodedRequestURI(uri); |
| request.getSessionInternal(true).setNote(Constants.FORM_REQUEST_NOTE, saved); |
| response.sendRedirect(response.encodeRedirectURL(uri)); |
| } |
| return false; |
| } |
| |
| register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password); |
| |
| // Redirect the user to the original request URI (which will cause |
| // the original request to be restored) |
| requestURI = savedRequestURL(session); |
| if (log.isTraceEnabled()) { |
| log.trace("Redirecting to original '" + requestURI + "'"); |
| } |
| if (requestURI == null) { |
| if (landingPage == null) { |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, sm.getString("authenticator.formlogin")); |
| } else { |
| // Make the authenticator think the user originally requested |
| // the landing page |
| String uri = request.getContextPath() + landingPage; |
| SavedRequest saved = new SavedRequest(); |
| saved.setMethod("GET"); |
| saved.setRequestURI(uri); |
| saved.setDecodedRequestURI(uri); |
| session.setNote(Constants.FORM_REQUEST_NOTE, saved); |
| response.sendRedirect(response.encodeRedirectURL(uri)); |
| } |
| } else { |
| String location = response.encodeRedirectURL(requestURI); |
| if ("HTTP/1.1".equals(request.getProtocol())) { |
| response.sendRedirect(location, HttpServletResponse.SC_SEE_OTHER); |
| } else { |
| response.sendRedirect(location, HttpServletResponse.SC_FOUND); |
| } |
| } |
| return false; |
| } |
| |
| |
| @Override |
| protected boolean isContinuationRequired(Request request) { |
| // Special handling for form-based logins to deal with the case |
| // where the login form (and therefore the "j_security_check" URI |
| // to which it submits) might be outside the secured area |
| String contextPath = this.context.getPath(); |
| String decodedRequestURI = request.getDecodedRequestURI(); |
| if (decodedRequestURI.startsWith(contextPath) && decodedRequestURI.endsWith(Constants.FORM_ACTION)) { |
| return true; |
| } |
| |
| // Special handling for form-based logins to deal with the case where |
| // a resource is protected for some HTTP methods but not protected for |
| // GET which is used after authentication when redirecting to the |
| // protected resource. |
| // TODO: This is similar to the FormAuthenticator.matchRequest() logic |
| // Is there a way to remove the duplication? |
| Session session = request.getSessionInternal(false); |
| if (session != null) { |
| SavedRequest savedRequest = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); |
| if (savedRequest != null && decodedRequestURI.equals(savedRequest.getDecodedRequestURI())) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| @Override |
| protected String getAuthMethod() { |
| return HttpServletRequest.FORM_AUTH; |
| } |
| |
| |
| @Override |
| protected void register(Request request, HttpServletResponse response, Principal principal, String authType, |
| String username, String password, boolean alwaysUseSession, boolean cache) { |
| |
| super.register(request, response, principal, authType, username, password, alwaysUseSession, cache); |
| |
| // If caching an authenticated Principal is turned off, |
| // store username and password as session notes to use them for re-authentication. |
| if (!cache) { |
| Session session = request.getSessionInternal(false); |
| if (session != null) { |
| if (username != null) { |
| session.setNote(Constants.SESS_USERNAME_NOTE, username); |
| } else { |
| session.removeNote(Constants.SESS_USERNAME_NOTE); |
| } |
| if (password != null) { |
| session.setNote(Constants.SESS_PASSWORD_NOTE, password); |
| } else { |
| session.removeNote(Constants.SESS_PASSWORD_NOTE); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Called to forward to the login page |
| * |
| * @param request Request we are processing |
| * @param response Response we are populating |
| * @param config Login configuration describing how authentication should be performed |
| * |
| * @throws IOException If the forward to the login page fails and the call to |
| * {@link HttpServletResponse#sendError(int, String)} throws an {@link IOException} |
| */ |
| protected void forwardToLoginPage(Request request, HttpServletResponse response, LoginConfig config) |
| throws IOException { |
| |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("formAuthenticator.forwardLogin", request.getRequestURI(), request.getMethod(), |
| config.getLoginPage(), context.getName())); |
| } |
| |
| String loginPage = config.getLoginPage(); |
| if (loginPage == null || loginPage.length() == 0) { |
| String msg = sm.getString("formAuthenticator.noLoginPage", context.getName()); |
| log.warn(msg); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg); |
| return; |
| } |
| |
| if (getChangeSessionIdOnAuthentication()) { |
| Session session = request.getSessionInternal(false); |
| if (session != null) { |
| String oldSessionId = session.getId(); |
| String newSessionId = changeSessionID(request, session); |
| session.setNote(Constants.SESSION_ID_NOTE, newSessionId); |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("formAuthenticator.changeSessionIdLogin", oldSessionId, newSessionId)); |
| } |
| } |
| } |
| |
| // Always use GET for the login page, regardless of the method used |
| String oldMethod = request.getMethod(); |
| request.getCoyoteRequest().method().setString("GET"); |
| |
| RequestDispatcher disp = context.getServletContext().getRequestDispatcher(loginPage); |
| try { |
| if (context.fireRequestInitEvent(request.getRequest())) { |
| disp.forward(request.getRequest(), response); |
| context.fireRequestDestroyEvent(request.getRequest()); |
| } |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| String msg = sm.getString("formAuthenticator.forwardLoginFail"); |
| log.warn(msg, t); |
| request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg); |
| } finally { |
| // Restore original method so that it is written into access log |
| request.getCoyoteRequest().method().setString(oldMethod); |
| } |
| } |
| |
| |
| /** |
| * Called to forward to the error page |
| * |
| * @param request Request we are processing |
| * @param response Response we are populating |
| * @param config Login configuration describing how authentication should be performed |
| * |
| * @throws IOException If the forward to the error page fails and the call to |
| * {@link HttpServletResponse#sendError(int, String)} throws an {@link IOException} |
| */ |
| protected void forwardToErrorPage(Request request, HttpServletResponse response, LoginConfig config) |
| throws IOException { |
| |
| String errorPage = config.getErrorPage(); |
| if (errorPage == null || errorPage.length() == 0) { |
| String msg = sm.getString("formAuthenticator.noErrorPage", context.getName()); |
| log.warn(msg); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg); |
| return; |
| } |
| |
| RequestDispatcher disp = context.getServletContext().getRequestDispatcher(config.getErrorPage()); |
| try { |
| if (context.fireRequestInitEvent(request.getRequest())) { |
| disp.forward(request.getRequest(), response); |
| context.fireRequestDestroyEvent(request.getRequest()); |
| } |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| String msg = sm.getString("formAuthenticator.forwardErrorFail"); |
| log.warn(msg, t); |
| request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg); |
| } |
| } |
| |
| |
| /** |
| * Does this request match the saved one (so that it must be the redirect we signaled after successful |
| * authentication? |
| * |
| * @param request The request to be verified |
| * |
| * @return <code>true</code> if the requests matched the saved one |
| */ |
| protected boolean matchRequest(Request request) { |
| // Has a session been created? |
| Session session = request.getSessionInternal(false); |
| if (session == null) { |
| return false; |
| } |
| |
| // Is there a saved request? |
| SavedRequest sreq = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); |
| if (sreq == null) { |
| return false; |
| } |
| |
| // Is there a saved principal? |
| if (cache && session.getPrincipal() == null || !cache && request.getPrincipal() == null) { |
| return false; |
| } |
| |
| // Does session id match? |
| if (getChangeSessionIdOnAuthentication()) { |
| String expectedSessionId = (String) session.getNote(Constants.SESSION_ID_NOTE); |
| if (expectedSessionId == null || !expectedSessionId.equals(request.getRequestedSessionId())) { |
| return false; |
| } |
| } |
| |
| // Does the request URI match? |
| String decodedRequestURI = request.getDecodedRequestURI(); |
| if (decodedRequestURI == null) { |
| return false; |
| } |
| return decodedRequestURI.equals(sreq.getDecodedRequestURI()); |
| } |
| |
| |
| /** |
| * Restore the original request from information stored in our session. If the original request is no longer present |
| * (because the session timed out), return <code>false</code>; otherwise, return <code>true</code>. |
| * |
| * @param request The request to be restored |
| * @param session The session containing the saved information |
| * |
| * @return <code>true</code> if the request was successfully restored |
| * |
| * @throws IOException if an IO error occurred during the process |
| */ |
| protected boolean restoreRequest(Request request, Session session) throws IOException { |
| |
| // Retrieve and remove the SavedRequest object from our session |
| SavedRequest saved = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); |
| session.removeNote(Constants.FORM_REQUEST_NOTE); |
| session.removeNote(Constants.SESSION_ID_NOTE); |
| if (saved == null) { |
| return false; |
| } |
| |
| // Swallow any request body since we will be replacing it |
| // Need to do this before headers are restored as AJP connector uses |
| // content length header to determine how much data needs to be read for |
| // request body |
| byte[] buffer = new byte[4096]; |
| InputStream is = request.createInputStream(); |
| while (is.read(buffer) >= 0) { |
| // Ignore request body |
| } |
| |
| // Modify our current request to reflect the original one |
| request.clearCookies(); |
| Iterator<Cookie> cookies = saved.getCookies(); |
| while (cookies.hasNext()) { |
| request.addCookie(cookies.next()); |
| } |
| |
| String method = saved.getMethod(); |
| MimeHeaders rmh = request.getCoyoteRequest().getMimeHeaders(); |
| rmh.recycle(); |
| boolean cacheable = "GET".equalsIgnoreCase(method) || "HEAD".equalsIgnoreCase(method); |
| Iterator<String> names = saved.getHeaderNames(); |
| while (names.hasNext()) { |
| String name = names.next(); |
| // The browser isn't expecting this conditional response now. |
| // Assuming that it can quietly recover from an unexpected 412. |
| // BZ 43687 |
| if (!("If-Modified-Since".equalsIgnoreCase(name) || |
| (cacheable && "If-None-Match".equalsIgnoreCase(name)))) { |
| Iterator<String> values = saved.getHeaderValues(name); |
| while (values.hasNext()) { |
| rmh.addValue(name).setString(values.next()); |
| } |
| } |
| } |
| |
| request.clearLocales(); |
| Iterator<Locale> locales = saved.getLocales(); |
| while (locales.hasNext()) { |
| request.addLocale(locales.next()); |
| } |
| |
| request.getCoyoteRequest().getParameters().recycle(); |
| |
| ByteChunk body = saved.getBody(); |
| |
| if (body != null) { |
| request.getCoyoteRequest().action(ActionCode.REQ_SET_BODY_REPLAY, body); |
| |
| // Set content type |
| MessageBytes contentType = MessageBytes.newInstance(); |
| |
| // If no content type specified, use default for POST |
| String savedContentType = saved.getContentType(); |
| if (savedContentType == null && "POST".equalsIgnoreCase(method)) { |
| savedContentType = "application/x-www-form-urlencoded"; |
| } |
| |
| contentType.setString(savedContentType); |
| request.getCoyoteRequest().setContentType(contentType); |
| } |
| |
| request.getCoyoteRequest().method().setString(method); |
| // The method, URI, queryString and protocol are normally stored as |
| // bytes in the HttpInputBuffer and converted lazily to String. At this |
| // point, the method has already been set as String in the line above |
| // but the URI, queryString and protocol are still in byte form in the |
| // HttpInputBuffer. Processing the saved request body will overwrite |
| // these bytes. Configuring the HttpInputBuffer to retain these bytes as |
| // it would in a normal request would require some invasive API changes. |
| // Therefore force the conversion to String now so the correct values |
| // are presented if the application requests them. |
| request.getCoyoteRequest().requestURI().toStringType(); |
| request.getCoyoteRequest().queryString().toStringType(); |
| request.getCoyoteRequest().protocol().toStringType(); |
| |
| if (saved.getOriginalMaxInactiveInterval() > 0) { |
| session.setMaxInactiveInterval(saved.getOriginalMaxInactiveInterval()); |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Save the original request information into our session. |
| * |
| * @param request The request to be saved |
| * @param session The session to contain the saved information |
| * |
| * @throws IOException if an IO error occurred during the process |
| */ |
| protected void saveRequest(Request request, Session session) throws IOException { |
| |
| // Create and populate a SavedRequest object for this request |
| SavedRequest saved = new SavedRequest(); |
| Cookie cookies[] = request.getCookies(); |
| if (cookies != null) { |
| for (Cookie cookie : cookies) { |
| saved.addCookie(cookie); |
| } |
| } |
| Enumeration<String> names = request.getHeaderNames(); |
| while (names.hasMoreElements()) { |
| String name = names.nextElement(); |
| Enumeration<String> values = request.getHeaders(name); |
| while (values.hasMoreElements()) { |
| String value = values.nextElement(); |
| saved.addHeader(name, value); |
| } |
| } |
| Enumeration<Locale> locales = request.getLocales(); |
| while (locales.hasMoreElements()) { |
| Locale locale = locales.nextElement(); |
| saved.addLocale(locale); |
| } |
| |
| // May need to acknowledge a 100-continue expectation |
| request.getResponse().sendAcknowledgement(ContinueResponseTiming.ALWAYS); |
| |
| int maxSavePostSize = request.getConnector().getMaxSavePostSize(); |
| if (maxSavePostSize != 0) { |
| ByteChunk body = new ByteChunk(); |
| body.setLimit(maxSavePostSize); |
| |
| byte[] buffer = new byte[4096]; |
| int bytesRead; |
| InputStream is = request.getInputStream(); |
| |
| while ((bytesRead = is.read(buffer)) >= 0) { |
| body.append(buffer, 0, bytesRead); |
| } |
| |
| // Only save the request body if there is something to save |
| if (body.getLength() > 0) { |
| saved.setContentType(request.getContentType()); |
| saved.setBody(body); |
| } |
| } |
| |
| saved.setMethod(request.getMethod()); |
| saved.setQueryString(request.getQueryString()); |
| saved.setRequestURI(request.getRequestURI()); |
| saved.setDecodedRequestURI(request.getDecodedRequestURI()); |
| |
| SavedRequest previousSavedRequest = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); |
| if (session.isNew()) { |
| int originalMaxInactiveInterval = session.getMaxInactiveInterval(); |
| if (originalMaxInactiveInterval > getAuthenticationSessionTimeout()) { |
| saved.setOriginalMaxInactiveInterval(originalMaxInactiveInterval); |
| session.setMaxInactiveInterval(getAuthenticationSessionTimeout()); |
| } |
| } else if (previousSavedRequest != null && previousSavedRequest.getOriginalMaxInactiveInterval() > 0) { |
| /* |
| * The user may have refreshed the browser page during authentication. Transfer the original max inactive |
| * interval from previous saved request to current one else, once authentication is completed, the session |
| * will retain the the shorter authentication session timeout |
| */ |
| saved.setOriginalMaxInactiveInterval(previousSavedRequest.getOriginalMaxInactiveInterval()); |
| } |
| |
| // Stash the SavedRequest in our session for later use |
| session.setNote(Constants.FORM_REQUEST_NOTE, saved); |
| } |
| |
| |
| /** |
| * Return the request URI (with the corresponding query string, if any) from the saved request so that we can |
| * redirect to it. |
| * |
| * @param session Our current session |
| * |
| * @return the original request URL |
| */ |
| protected String savedRequestURL(Session session) { |
| SavedRequest saved = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); |
| if (saved == null) { |
| return null; |
| } |
| StringBuilder sb = new StringBuilder(saved.getRequestURI()); |
| if (saved.getQueryString() != null) { |
| sb.append('?'); |
| sb.append(saved.getQueryString()); |
| } |
| |
| // Avoid protocol relative redirects |
| while (sb.length() > 1 && sb.charAt(1) == '/') { |
| sb.deleteCharAt(0); |
| } |
| |
| return sb.toString(); |
| } |
| } |