com.pdd.pop.ext.glassfish.grizzly.http.server.Request Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2008-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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 com.pdd.pop.ext.glassfish.grizzly.http.server;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import com.pdd.pop.ext.glassfish.grizzly.Buffer;
import com.pdd.pop.ext.glassfish.grizzly.Grizzly;
import com.pdd.pop.ext.glassfish.grizzly.ReadHandler;
import com.pdd.pop.ext.glassfish.grizzly.ThreadCache;
import com.pdd.pop.ext.glassfish.grizzly.WriteHandler;
import com.pdd.pop.ext.glassfish.grizzly.filterchain.FilterChainContext;
import com.pdd.pop.ext.glassfish.grizzly.http.Cookie;
import com.pdd.pop.ext.glassfish.grizzly.http.Cookies;
import com.pdd.pop.ext.glassfish.grizzly.http.HttpRequestPacket;
import com.pdd.pop.ext.glassfish.grizzly.http.Method;
import com.pdd.pop.ext.glassfish.grizzly.http.Note;
import com.pdd.pop.ext.glassfish.grizzly.http.Protocol;
import com.pdd.pop.ext.glassfish.grizzly.http.io.InputBuffer;
import com.pdd.pop.ext.glassfish.grizzly.http.io.NIOInputStream;
import com.pdd.pop.ext.glassfish.grizzly.http.io.NIOReader;
import com.pdd.pop.ext.glassfish.grizzly.http.server.io.ServerInputBuffer;
import com.pdd.pop.ext.glassfish.grizzly.http.server.util.Globals;
import com.pdd.pop.ext.glassfish.grizzly.http.server.util.MappingData;
import com.pdd.pop.ext.glassfish.grizzly.http.server.util.ParameterMap;
import com.pdd.pop.ext.glassfish.grizzly.http.server.util.RequestUtils;
import com.pdd.pop.ext.glassfish.grizzly.http.server.util.SimpleDateFormats;
import com.pdd.pop.ext.glassfish.grizzly.http.server.util.StringParser;
import com.pdd.pop.ext.glassfish.grizzly.http.util.Chunk;
import com.pdd.pop.ext.glassfish.grizzly.http.util.DataChunk;
import com.pdd.pop.ext.glassfish.grizzly.http.util.FastHttpDateFormat;
import com.pdd.pop.ext.glassfish.grizzly.http.util.Header;
import com.pdd.pop.ext.glassfish.grizzly.http.util.Parameters;
import com.pdd.pop.ext.glassfish.grizzly.localization.LogMessages;
import com.pdd.pop.ext.glassfish.grizzly.utils.Charsets;
import com.pdd.pop.ext.glassfish.grizzly.utils.JdkVersion;
import static com.pdd.pop.ext.glassfish.grizzly.http.util.Constants.FORM_POST_CONTENT_TYPE;
/**
* Wrapper object for the Coyote request.
*
* @author Remy Maucherat
* @author Craig R. McClanahan
* @version $Revision: 1.2 $ $Date: 2007/03/14 02:15:42 $
*/
public class Request {
// @TODO remove this property support once we're sure nobody
// relies on this functionality.
private static final Boolean FORCE_CLIENT_AUTH_ON_GET_USER_PRINCIPAL =
Boolean.getBoolean(Request.class.getName() + ".force-client-auth-on-get-user-principal");
private static final Logger LOGGER = Grizzly.logger(Request.class);
private static final ThreadCache.CachedTypeIndex CACHE_IDX =
ThreadCache.obtainIndex(Request.class, 16);
private static final LocaleParser localeParser;
static {
LocaleParser lp;
final JdkVersion version = JdkVersion.getJdkVersion();
if (version.compareTo("1.7.0") >= 0) {
try {
@SuppressWarnings("unchecked")
Class extends LocaleParser> localeParserClazz =
(Class extends LocaleParser>)
Class.forName("com.pdd.pop.ext.glassfish.grizzly.http.server.TagLocaleParser");
lp = localeParserClazz.newInstance();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Can't load JDK7 TagLocaleParser", e);
}
lp = new LegacyLocaleParser();
}
} else {
lp = new LegacyLocaleParser();
}
localeParser = lp;
assert localeParser != null;
}
public static Request create() {
final Request request =
ThreadCache.takeFromCache(CACHE_IDX);
if (request != null) {
return request;
}
return new Request(new Response());
}
/**
* Request attribute will be associated with a boolean value indicating
* whether or not it's possible to transfer a {@link java.io.File} using sendfile.
*
* @since 2.2
*/
public static final String SEND_FILE_ENABLED_ATTR = "com.pdd.pop.ext.glassfish.grizzly.http.SEND_FILE_ENABLED";
/**
*
* The value of this request attribute, as set by the developer must be a {@link java.io.File}
* that exists, is not a directory, and is readable. This {@link java.io.File} will be
* transferred using sendfile if {@link #SEND_FILE_ENABLED_ATTR} is true. If sendfile
* support isn't enabled, an IllegalStateException will be raised at runtime.
* The {@link HttpHandler} using this functionality should refrain from writing content
* via the response.
*
*
*
* Note that once this attribute is set, the sendfile process will begin.
*
*
* @since 2.2
*/
public static final String SEND_FILE_ATTR = "com.pdd.pop.ext.glassfish.grizzly.http.SEND_FILE";
/**
*
* The value of this request attribute signifies the starting offset of the file
* transfer. If not specified, an offset of zero will be assumed. The type of
* the value must be {@link Long}.
*
*
*
* NOTE: In order for this attribute to take effect, it must be
* set before the {@link #SEND_FILE_ATTR} is set.
*
*
* @since 2.2
*/
public static final String SEND_FILE_START_OFFSET_ATTR = "com.pdd.pop.ext.glassfish.grizzly.http.FILE_START_OFFSET";
/**
*
* The value of this request attribute signifies the total number of bytes to
* transfer. If not specified, the entire file will be transferred.
* The type of the value must be {@link Long}
*
*
*
* NOTE: In order for this attribute to take effect, it must be
* set before the {@link #SEND_FILE_ATTR} is set.
*
*
* @since 2.2
*/
public static final String SEND_FILE_WRITE_LEN_ATTR = "com.pdd.pop.ext.glassfish.grizzly.http.FILE_WRITE_LEN";
// ------------------------------------------------------------- Properties
/**
* The match string for identifying a session ID parameter.
*/
private static final String match =
';' + Globals.SESSION_PARAMETER_NAME + '=';
// -------------------------------------------------------------------- //
public final MappingData obtainMappingData() {
if (cachedMappingData == null) {
cachedMappingData = new MappingData();
}
return cachedMappingData;
}
// --------------------------------------------------------------------- //
// ----------------------------------------------------- Instance Variables
/**
* HTTP Request Packet
*/
protected HttpRequestPacket request;
protected FilterChainContext ctx;
protected HttpServerFilter httpServerFilter;
protected final List afterServicesList =
new ArrayList(4);
private Session session;
/**
* The HTTP request scheme.
*/
private String scheme;
private final PathData contextPath = new PathData(this, "", null);
private final PathData httpHandlerPath = new PathData(this);
private final PathData pathInfo = new PathData(this);
private MappingData cachedMappingData;
/**
* The set of cookies associated with this Request.
*/
protected Cookie[] cookies = null;
protected Cookies rawCookies;
// Session cookie name
protected String sessionCookieName;
// Session manager
protected SessionManager sessionManager;
/**
* The default Locale if none are specified.
*/
protected static final Locale defaultLocale = Locale.getDefault();
/**
* The preferred Locales associated with this Request.
*/
protected final ArrayList locales = new ArrayList();
/**
* The current dispatcher type.
*/
protected Object dispatcherType = null;
/**
* The associated input buffer.
*/
protected final ServerInputBuffer inputBuffer = new ServerInputBuffer();
/**
* NIOInputStream.
*/
private final NIOInputStreamImpl inputStream = new NIOInputStreamImpl();
/**
* Reader.
*/
private final NIOReaderImpl reader = new NIOReaderImpl();
/**
* Using stream flag.
*/
protected boolean usingInputStream = false;
/**
* Using writer flag.
*/
protected boolean usingReader = false;
/**
* User principal.
*/
protected Principal userPrincipal = null;
/**
* Session parsed flag.
*/
protected boolean sessionParsed = false;
/**
* Request parameters parsed flag.
*/
protected boolean requestParametersParsed = false;
/**
* Cookies parsed flag.
*/
protected boolean cookiesParsed = false;
/**
* Secure flag.
*/
protected boolean secure = false;
/**
* The Subject associated with the current AccessControllerContext
*/
protected Subject subject = null;
/**
* Hash map used in the getParametersMap method.
*/
protected final ParameterMap parameterMap = new ParameterMap();
protected final Parameters parameters = new Parameters();
/**
* The current request dispatcher path.
*/
protected Object requestDispatcherPath = null;
/**
* Was the requested session ID received in a cookie?
*/
protected boolean requestedSessionCookie = false;
/**
* The requested session ID (if any) for this request.
*/
protected String requestedSessionId = null;
/**
* Was the requested session ID received in a URL?
*/
protected boolean requestedSessionURL = false;
/**
* Parse locales.
*/
protected boolean localesParsed = false;
/**
* The string parser we will use for parsing request lines.
*/
private StringParser parser;
// START S1AS 4703023
/**
* The current application dispatch depth.
*/
private int dispatchDepth = 0;
/**
* The maximum allowed application dispatch depth.
*/
private static int maxDispatchDepth = Constants.DEFAULT_MAX_DISPATCH_DEPTH;
// END S1AS 4703023
// START SJSAS 6346226
private String jrouteId;
// END SJSAS 6346226
/**
* The {@link RequestExecutorProvider} responsible for executing user's code
* in {@link HttpHandler#service(com.pdd.pop.ext.glassfish.grizzly.http.server.Request, com.pdd.pop.ext.glassfish.grizzly.http.server.Response)}
* and notifying {@link ReadHandler}, {@link WriteHandler} registered by the user
*/
private RequestExecutorProvider requestExecutorProvider;
/**
* The response with which this request is associated.
*/
protected final Response response;
// ----------------------------------------------------------- Constructors
/**
* Temporarily introduce public constructor to fix GRIZZLY-1782.
* Just to make request instances proxiable.
* This constructor is not intended for client code consumption
* and should not be used explicitly as it creates an invalid instance.
*
* @deprecated
*/
public Request() {
this.response = null;
}
protected Request(final Response response) {
this.response = response;
}
// --------------------------------------------------------- Public Methods
public void initialize(final HttpRequestPacket request,
final FilterChainContext ctx,
final HttpServerFilter httpServerFilter) {
this.request = request;
this.ctx = ctx;
this.httpServerFilter = httpServerFilter;
inputBuffer.initialize(this, ctx);
parameters.setHeaders(request.getHeaders());
parameters.setQuery(request.getQueryStringDC());
final DataChunk remoteUser = request.remoteUser();
if (httpServerFilter != null) {
final ServerFilterConfiguration configuration =
httpServerFilter.getConfiguration();
parameters.setQueryStringEncoding(configuration.getDefaultQueryEncoding());
final BackendConfiguration backendConfiguration =
configuration.getBackendConfiguration();
if (backendConfiguration != null) {
// Set the protocol scheme based on backend config
if (backendConfiguration.getScheme() != null) {
scheme = backendConfiguration.getScheme();
} else if (backendConfiguration.getSchemeMapping() != null) {
scheme = request.getHeader(backendConfiguration.getSchemeMapping());
}
if ("https".equalsIgnoreCase(scheme)) {
// this ensures that JSESSIONID cookie has the "Secure" attribute
// when using scheme-mapping
request.setSecure(true);
}
if (remoteUser.isNull()
&& backendConfiguration.getRemoteUserMapping() != null) {
remoteUser.setString(request.getHeader(
backendConfiguration.getRemoteUserMapping()));
}
}
}
if (scheme == null) {
scheme = request.isSecure() ? "https" : "http";
}
if (!remoteUser.isNull()) {
setUserPrincipal(new GrizzlyPrincipal(remoteUser.toString()));
}
}
final HttpServerFilter getServerFilter() {
return httpServerFilter;
}
/**
* @return the Coyote request.
*/
public HttpRequestPacket getRequest() {
return this.request;
}
/**
* @return the Response with which this Request is associated.
*/
public Response getResponse() {
return response;
}
/**
* @return session cookie name, if not set default JSESSIONID name will be used
*/
public String getSessionCookieName() {
return sessionCookieName;
}
/**
* Set the session cookie name, if not set default JSESSIONID name will be used
* @param sessionCookieName
*/
public void setSessionCookieName(String sessionCookieName) {
this.sessionCookieName = sessionCookieName;
}
/**
* @return {@link #sessionCookieName} if set, or {@link Globals#SESSION_COOKIE_NAME} if
* {@link #sessionCookieName} is not set
*/
protected String obtainSessionCookieName() {
return sessionCookieName != null ? sessionCookieName : Globals.SESSION_COOKIE_NAME;
}
/**
* @return {@link SessionManager}
*/
protected SessionManager getSessionManager() {
return sessionManager != null
? sessionManager
: DefaultSessionManager.instance();
}
/**
* Set {@link SessionManager}, null value implies {@link DefaultSessionManager}
* @param sessionManager
*/
protected void setSessionManager(final SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
/**
* @return the {@link Executor} responsible for notifying {@link ReadHandler},
* {@link WriteHandler} associated with this Request processing.
*/
public Executor getRequestExecutor() {
return requestExecutorProvider.getExecutor(this);
}
/**
* Sets @return the {@link RequestExecutorProvider} responsible for executing
* user's code in {@link HttpHandler#service(com.pdd.pop.ext.glassfish.grizzly.http.server.Request, com.pdd.pop.ext.glassfish.grizzly.http.server.Response)}
* and notifying {@link ReadHandler}, {@link WriteHandler} registered by the user.
*
* @param requestExecutorProvider {@link RequestExecutorProvider}
*/
protected void setRequestExecutorProvider(
final RequestExecutorProvider requestExecutorProvider) {
this.requestExecutorProvider = requestExecutorProvider;
}
/**
* Add the listener, which will be notified, once Request processing will be finished.
* @param listener the listener, which will be notified, once Request processing will be finished.
*/
public void addAfterServiceListener(final AfterServiceListener listener) {
afterServicesList.add(listener);
}
/**
* Remove the "after-service" listener, which was previously added by {@link #addAfterServiceListener(com.pdd.pop.ext.glassfish.grizzly.http.server.AfterServiceListener)}.
* @param listener the "after-service" listener, which was previously added by {@link #addAfterServiceListener(com.pdd.pop.ext.glassfish.grizzly.http.server.AfterServiceListener)}.
*/
public void removeAfterServiceListener(final AfterServiceListener listener) {
afterServicesList.remove(listener);
}
protected void onAfterService() {
if (!inputBuffer.isFinished()) {
inputBuffer.terminate();
}
if (!afterServicesList.isEmpty()) {
for (int i = 0, size = afterServicesList.size(); i < size; i++) {
final AfterServiceListener anAfterServicesList = afterServicesList.get(i);
try {
anAfterServicesList.onAfterService(this);
} catch (Exception e) {
LOGGER.log(Level.WARNING,
LogMessages.WARNING_GRIZZLY_HTTP_SERVER_REQUEST_AFTERSERVICE_NOTIFICATION_ERROR(), e);
}
}
}
}
/**
* Release all object references, and initialize instance variables, in
* preparation for reuse of this object.
*/
protected void recycle() {
scheme = null;
contextPath.setPath("");
httpHandlerPath.reset();
pathInfo.reset();
dispatcherType = null;
requestDispatcherPath = null;
inputBuffer.recycle();
inputStream.recycle();
reader.recycle();
usingInputStream = false;
usingReader = false;
userPrincipal = null;
subject = null;
sessionParsed = false;
requestParametersParsed = false;
cookiesParsed = false;
if (rawCookies != null) {
rawCookies.recycle();
}
locales.clear();
localesParsed = false;
secure = false;
request.recycle();
request = null;
ctx = null;
httpServerFilter = null;
cookies = null;
requestedSessionId = null;
sessionCookieName = null;
sessionManager = null;
session = null;
dispatchDepth = 0; // S1AS 4703023
parameterMap.setLocked(false);
parameterMap.clear();
parameters.recycle();
requestExecutorProvider = null;
afterServicesList.clear();
// Notes holder shouldn't be recycled.
// notesHolder.recycle();
if (cachedMappingData != null) {
cachedMappingData.recycle();
}
ThreadCache.putToCache(CACHE_IDX, this);
}
// -------------------------------------------------------- Request Methods
/**
* Return the authorization credentials sent with this request.
*/
public String getAuthorization() {
return request.getHeader(Constants.AUTHORIZATION_HEADER);
}
// ------------------------------------------------- Request Public Methods
/**
* Replays request's payload by setting new payload {@link Buffer}.
* If request parameters have been parsed based on prev. request's POST
* payload - the parameters will be recycled and ready to be parsed again.
*
* @param buffer payload
*
* @throws IllegalStateException, if previous request payload has not been read off.
*/
public void replayPayload(final Buffer buffer) {
inputBuffer.replayPayload(buffer);
usingReader = false;
usingInputStream = false;
if (Method.POST.equals(getMethod()) && requestParametersParsed) {
requestParametersParsed = false;
parameterMap.setLocked(false);
parameterMap.clear();
parameters.recycle();
}
}
/**
* Create and return a NIOInputStream to read the content
* associated with this Request.
*
* @return {@link NIOInputStream}
*/
public NIOInputStream createInputStream() {
inputStream.setInputBuffer(inputBuffer);
return inputStream;
}
/**
* Create a named {@link Note} associated with this Request.
*
* @param the {@link Note} type.
* @param name the {@link Note} name.
* @return the {@link Note}.
*/
@SuppressWarnings({"unchecked"})
public static Note createNote(final String name) {
return HttpRequestPacket.createNote(name);
}
/**
* Return the {@link Note} value associated with this Request,
* or null
if no such binding exists.
* Use {@link #createNote(java.lang.String)} to create a new {@link Note}.
*
* @param note {@link Note} value to be returned
*/
public E getNote(final Note note) {
return request.getNote(note);
}
/**
* Return a {@link Set} containing the String names of all note bindings
* that exist for this request.
* Use {@link #createNote(java.lang.String)} to create a new {@link Note}.
*
* @return a {@link Set} containing the String names of all note bindings
* that exist for this request.
*/
public Set getNoteNames() {
return request.getNoteNames();
}
/**
* Remove the {@link Note} value associated with this request.
* Use {@link #createNote(java.lang.String)} to create a new {@link Note}.
*
* @param note {@link Note} value to be removed
*/
public E removeNote(final Note note) {
return request.removeNote(note);
}
/**
* Bind the {@link Note} value to this Request,
* replacing any existing binding for this name.
* Use {@link #createNote(java.lang.String)} to create a new {@link Note}.
*
* @param note {@link Note} to which the object should be bound
* @param value the {@link Note} value be bound to the specified {@link Note}.
*/
public void setNote(final Note note, final E value) {
request.setNote(note, value);
}
/**
* Set the name of the server (virtual host) to process this request.
*
* @param name The server name
*/
public void setServerName(String name) {
request.serverName().setString(name);
}
/**
* Set the port number of the server to process this request.
*
* @param port The server port
*/
public void setServerPort(int port) {
request.setServerPort(port);
}
/**
* @return {@link HttpServerFilter}, which dispatched this request
*/
public HttpServerFilter getHttpFilter() {
return httpServerFilter;
}
/**
* Returns the portion of the request URI that indicates the context of the request.
* The context path always comes first in a request URI.
* The path starts with a "/" character but does not end with a "/" character.
* For {@link HttpHandler}s in the default (root) context, this method returns "".
* The container does not decode this string.
*
* @return a String specifying the portion of the request URI that indicates the context of the request
*/
public String getContextPath() {
return contextPath.get();
}
protected void setContextPath(final String contextPath) {
this.contextPath.setPath(contextPath);
}
protected void setContextPath(final PathResolver contextPath) {
this.contextPath.setResolver(contextPath);
}
/**
* Returns the part of this request's URL that calls the HttpHandler.
* This includes either the HttpHandler name or a path to the HttpHandler,
* but does not include any extra path information or a query string.
*
* @return a String containing the name or path of the HttpHandler being
* called, as specified in the request URL
* @throws IllegalStateException if HttpHandler path was not set explicitly
* and attempt to URI-decode {@link com.pdd.pop.ext.glassfish.grizzly.http.util.RequestURIRef#getDecodedURI()}
* failed.
*/
public String getHttpHandlerPath() {
return httpHandlerPath.get();
}
protected void setHttpHandlerPath(final String httpHandlerPath) {
this.httpHandlerPath.setPath(httpHandlerPath);
}
protected void setHttpHandlerPath(final PathResolver httpHandlerPath) {
this.httpHandlerPath.setResolver(httpHandlerPath);
}
/**
* Returns any extra path information associated with the URL the client
* sent when it made this request.
* The extra path information follows the HttpHandler path but precedes
* the query string. This method returns null if there was no extra path
* information.
*
* @return a String specifying extra path information that comes after the
* HttpHandler path but before the query string in the request URL;
* or null if the URL does not have any extra path information
*/
public String getPathInfo() {
return pathInfo.get();
}
protected void setPathInfo(final String pathInfo) {
this.pathInfo.setPath(pathInfo);
}
protected void setPathInfo(final PathResolver pathInfo) {
this.pathInfo.setResolver(pathInfo);
}
// ------------------------------------------------- ServletRequest Methods
/**
* Return the specified request attribute if it exists; otherwise, return
* null
.
*
* @param name Name of the request attribute to return
* @return the specified request attribute if it exists; otherwise, return
* null
.
*/
public Object getAttribute(final String name) {
if (SEND_FILE_ENABLED_ATTR.equals(name)) {
return response.isSendFileEnabled();
}
Object attribute = request.getAttribute(name);
if (attribute != null) {
return attribute;
}
if (Globals.SSL_CERTIFICATE_ATTR.equals(name)) {
attribute = RequestUtils.populateCertificateAttribute(this);
if (attribute != null) {
request.setAttribute(name, attribute);
}
} else if (isSSLAttribute(name)) {
RequestUtils.populateSSLAttributes(this);
attribute = request.getAttribute(name);
} else if (Globals.DISPATCHER_REQUEST_PATH_ATTR.equals(name)) {
return requestDispatcherPath;
}
return attribute;
}
/**
* Test if a given name is one of the special Servlet-spec SSL attributes.
*/
static boolean isSSLAttribute(final String name) {
return Globals.CERTIFICATES_ATTR.equals(name)
|| Globals.CIPHER_SUITE_ATTR.equals(name)
|| Globals.KEY_SIZE_ATTR.equals(name);
}
/**
* Return the names of all request attributes for this Request, or an
* empty {@link Set} if there are none.
*/
public Set getAttributeNames() {
return request.getAttributeNames();
}
/**
* Return the character encoding for this Request.
*/
public String getCharacterEncoding() {
return request.getCharacterEncoding();
}
/**
* Return the content length for this Request.
*/
public int getContentLength() {
return (int) request.getContentLength();
}
/**
* Return the content length for this Request represented by Java long type.
*/
public long getContentLengthLong() {
return request.getContentLength();
}
/**
* Return the content type for this Request.
*/
public String getContentType() {
return request.getContentType();
}
/**
*
* Return the {@link InputStream} for this {@link Request}.
*
*
* By default the returned {@link NIOInputStream} will work as blocking
* {@link InputStream}, but it will be possible to call {@link NIOInputStream#isReady()},
* {@link NIOInputStream#available()}, or {@link NIOInputStream#notifyAvailable(com.pdd.pop.ext.glassfish.grizzly.ReadHandler)}
* to avoid blocking.
*
* @return the {@link NIOInputStream} for this {@link Request}.
*
* @exception IllegalStateException if {@link #getReader()} or
* {@link #getNIOReader()} has already been called for this request.
*
* @since 2.2
*/
public InputStream getInputStream() {
return getNIOInputStream();
}
/**
*
* Return the {@link NIOInputStream} for this {@link Request}. This stream
* will not block when reading content.
*
*
*
* NOTE: For now, in order to use non-blocking functionality, this
* method must be invoked before the {@link HttpHandler#service(Request, Response)}
* method returns. We hope to have this addressed in the next release.
*
*
* @return the {@link NIOInputStream} for this {@link Request}.
*
* @exception IllegalStateException if {@link #getReader()} or
* {@link #getNIOReader()} has already been called for this request.
*/
public NIOInputStream getNIOInputStream() {
if (usingReader)
throw new IllegalStateException("Illegal attempt to call getInputStream() after getReader() has already been called.");
usingInputStream = true;
inputStream.setInputBuffer(inputBuffer);
return inputStream;
}
/**
* @return true
if the current input source is operating in
* non-blocking mode. In other words {@link #getNIOInputStream()} or
* {@link #getNIOReader()} were invoked.
* @deprecated will always return true
*/
public boolean asyncInput() {
return inputBuffer.isAsyncEnabled();
}
/**
* @return true
if this request requires acknowledgment.
*/
public boolean requiresAcknowledgement() {
return request.requiresAcknowledgement();
}
/**
* Return the preferred Locale that the client will accept content in,
* based on the value for the first Accept-Language
header
* that was encountered. If the request did not specify a preferred
* language, the server's default Locale is returned.
*/
public Locale getLocale() {
if (!localesParsed)
parseLocales();
if (!locales.isEmpty()) {
return (locales.get(0));
} else {
return (defaultLocale);
}
}
/**
* Return the set of preferred Locales that the client will accept
* content in, based on the values for any Accept-Language
* headers that were encountered. If the request did not specify a
* preferred language, the server's default Locale is returned.
*/
public List getLocales() {
if (!localesParsed)
parseLocales();
if (!locales.isEmpty())
return locales;
final ArrayList results = new ArrayList();
results.add(defaultLocale);
return results;
}
/**
* Returns the low-level parameters holder for finer control over parameters.
*
* @return {@link Parameters}.
*/
public Parameters getParameters() {
return parameters;
}
/**
* Return the value of the specified request parameter, if any; otherwise,
* return null
. If there is more than one value defined,
* return only the first one.
*
* @param name Name of the desired request parameter
*/
public String getParameter(final String name) {
if (!requestParametersParsed) {
parseRequestParameters();
}
return parameters.getParameter(name);
}
/**
* Returns a {@link java.util.Map} of the parameters of this request.
* Request parameters are extra information sent with the request.
* For HTTP servlets, parameters are contained in the query string
* or posted form data.
*
* @return A {@link java.util.Map} containing parameter names as keys
* and parameter values as map values.
*/
public Map getParameterMap() {
if (parameterMap.isLocked())
return parameterMap;
for (final String name : getParameterNames()) {
final String[] values = getParameterValues(name);
parameterMap.put(name, values);
}
parameterMap.setLocked(true);
return parameterMap;
}
/**
* Return the names of all defined request parameters for this request.
*/
public Set getParameterNames() {
if (!requestParametersParsed)
parseRequestParameters();
return parameters.getParameterNames();
}
/**
* Return the defined values for the specified request parameter, if any;
* otherwise, return null
.
*
* @param name Name of the desired request parameter
*/
public String[] getParameterValues(String name) {
if (!requestParametersParsed)
parseRequestParameters();
return parameters.getParameterValues(name);
}
/**
* Return the protocol and version used to make this Request.
*/
public Protocol getProtocol() {
return request.getProtocol();
}
/**
*
* Returns the {@link Reader} associated with this {@link Request}.
*
*
* By default the returned {@link NIOReader} will work as blocking
* {@link java.io.Reader}, but it will be possible to call {@link NIOReader#isReady()}
* or {@link NIOReader#notifyAvailable(com.pdd.pop.ext.glassfish.grizzly.ReadHandler)}
* to avoid blocking.
*
* @return the {@link NIOReader} associated with this {@link Request}.
*
* @throws IllegalStateException if {@link #getInputStream()} or
* {@link #getNIOInputStream()} has already been called for this request.
*
* @since 2.2
*/
public Reader getReader() {
return getNIOReader();
}
/**
*
* Returns the {@link NIOReader} associated with this {@link Request}.
* This {@link NIOReader} will not block while reading content.
*
*
* @return {@link NIOReader}
* @throws IllegalStateException if {@link #getInputStream()} or
* {@link #getNIOInputStream()} has already been called for this request.
*/
public NIOReader getNIOReader() {
if (usingInputStream)
throw new IllegalStateException("Illegal attempt to call getReader() after getInputStream() has alread been called.");
usingReader = true;
inputBuffer.processingChars();
reader.setInputBuffer(inputBuffer);
return reader;
}
/**
* Return the remote IP address making this Request.
*/
public String getRemoteAddr() {
return request.getRemoteAddress();
}
/**
* Return the remote host name making this Request.
*/
public String getRemoteHost() {
return request.getRemoteHost();
}
/**
* Returns the Internet Protocol (IP) source port of the client
* or last proxy that sent the request.
*/
public int getRemotePort(){
return request.getRemotePort();
}
/**
* Returns the host name of the Internet Protocol (IP) interface on
* which the request was received.
*/
public String getLocalName(){
return request.getLocalName();
}
/**
* Returns the Internet Protocol (IP) address of the interface on
* which the request was received.
*/
public String getLocalAddr(){
return request.getLocalAddress();
}
/**
* Returns the Internet Protocol (IP) port number of the interface
* on which the request was received.
*/
public int getLocalPort(){
return request.getLocalPort();
}
/**
* Return the scheme used to make this Request.
*/
public String getScheme() {
return scheme;
}
/**
* Return the server name responding to this Request.
*/
public String getServerName() {
return request.serverName().toString();
}
/**
* Return the server port responding to this Request.
*/
public int getServerPort() {
return request.getServerPort();
}
/**
* Was this request received on a secure connection?
*/
public boolean isSecure() {
return request.isSecure();
}
/**
* Remove the specified request attribute if it exists.
*
* @param name Name of the request attribute to remove
*/
public void removeAttribute(String name) {
request.removeAttribute(name);
}
/**
* Set the specified request attribute to the specified value.
*
* @param name Name of the request attribute to set
* @param value The associated value
*/
public void setAttribute(final String name, final Object value) {
// Name cannot be null
if (name == null)
throw new IllegalArgumentException("Argument 'name' cannot be null");
// Null value is the same as removeAttribute()
if (value == null) {
removeAttribute(name);
return;
}
if (name.equals(Globals.DISPATCHER_TYPE_ATTR)) {
dispatcherType = value;
return;
} else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) {
requestDispatcherPath = value;
return;
}
request.setAttribute(name, value);
if (response.isSendFileEnabled() && SEND_FILE_ATTR.equals(name)) {
RequestUtils.handleSendFile(this);
}
}
/**
* Overrides the name of the character encoding used in the body of this
* request.
*
* This method must be called prior to reading request parameters or
* reading input using getReader()
. Otherwise, it has no
* effect.
*
* @param encoding String
containing the name of
* the character encoding.
* @throws java.io.UnsupportedEncodingException if this
* ServletRequest is still in a state where a
* character encoding may be set, but the specified
* encoding is invalid
*
* @since Servlet 2.3
*/
@SuppressWarnings({"unchecked"})
public void setCharacterEncoding(final String encoding)
throws UnsupportedEncodingException {
// START SJSAS 4936855
if (requestParametersParsed || usingReader) {
return;
}
// END SJSAS 4936855
Charsets.lookupCharset(encoding);
// Save the validated encoding
request.setCharacterEncoding(encoding);
}
// START S1AS 4703023
/**
* Static setter method for the maximum dispatch depth
*/
public static void setMaxDispatchDepth(int depth) {
maxDispatchDepth = depth;
}
public static int getMaxDispatchDepth(){
return maxDispatchDepth;
}
/**
* Increment the depth of application dispatch
*/
public int incrementDispatchDepth() {
return ++dispatchDepth;
}
/**
* Decrement the depth of application dispatch
*/
public int decrementDispatchDepth() {
return --dispatchDepth;
}
/**
* Check if the application dispatching has reached the maximum
*/
public boolean isMaxDispatchDepthReached() {
return dispatchDepth > maxDispatchDepth;
}
// END S1AS 4703023
// ---------------------------------------------------- HttpRequest Methods
/**
* Add a Cookie to the set of Cookies associated with this Request.
*
* @param cookie The new cookie
*/
public void addCookie(Cookie cookie) {
// For compatibility only
if (!cookiesParsed)
parseCookies();
int size = 0;
if (cookie != null) {
size = cookies.length;
}
Cookie[] newCookies = new Cookie[size + 1];
System.arraycopy(cookies, 0, newCookies, 0, size);
newCookies[size] = cookie;
cookies = newCookies;
}
/**
* Add a Locale to the set of preferred Locales for this Request. The
* first added Locale will be the first one returned by getLocales().
*
* @param locale The new preferred Locale
*/
public void addLocale(Locale locale) {
locales.add(locale);
}
/**
* Add a parameter name and corresponding set of values to this Request.
* (This is used when restoring the original request on a form based
* login).
*
* @param name Name of this request parameter
* @param values Corresponding values for this request parameter
*/
public void addParameter(String name, String values[]) {
parameters.addParameterValues(name, values);
}
/**
* Clear the collection of Cookies associated with this Request.
*/
public void clearCookies() {
cookiesParsed = true;
cookies = null;
}
/**
* Clear the collection of Headers associated with this Request.
*/
public void clearHeaders() {
// Not used
}
/**
* Clear the collection of Locales associated with this Request.
*/
public void clearLocales() {
locales.clear();
}
/**
* Clear the collection of parameters associated with this Request.
*/
public void clearParameters() {
// Not used
}
/**
* Get the decoded request URI.
*
* @return the URL decoded request URI
*/
public String getDecodedRequestURI() throws CharConversionException {
return request.getRequestURIRef().getDecodedURI();
}
/**
* Set the Principal who has been authenticated for this Request. This
* value is also used to calculate the value to be returned by the
* getRemoteUser()
method.
*
* @param principal The user Principal
*/
public void setUserPrincipal(Principal principal) {
this.userPrincipal = principal;
}
// --------------------------------------------- HttpServletRequest Methods
/**
* Return the authentication type used for this Request.
*/
public String getAuthType() {
return request.authType().toString();
}
/**
* Return the set of Cookies received with this Request.
*/
public Cookie[] getCookies() {
if (!cookiesParsed)
parseCookies();
return cookies;
}
/**
* Set the set of cookies received with this Request.
*/
public void setCookies(Cookie[] cookies) {
this.cookies = cookies;
}
/**
* Return the value of the specified date header, if any; otherwise
* return -1.
*
* @param name Name of the requested date header
*
* @exception IllegalArgumentException if the specified header value
* cannot be converted to a date
*/
public long getDateHeader(String name) {
String value = getHeader(name);
if (value == null)
return (-1L);
final SimpleDateFormats formats = SimpleDateFormats.create();
try {
// Attempt to convert the date header in a variety of formats
long result = FastHttpDateFormat.parseDate(value, formats.getFormats());
if (result != (-1L)) {
return result;
}
throw new IllegalArgumentException(value);
} finally {
formats.recycle();
}
}
/**
* Return the value of the specified date header, if any; otherwise
* return -1.
*
* @param header the requested date {@link Header}
*
* @exception IllegalArgumentException if the specified header value
* cannot be converted to a date
*
* @since 2.1.2
*/
public long getDateHeader(Header header) {
String value = getHeader(header);
if (value == null)
return (-1L);
final SimpleDateFormats formats = SimpleDateFormats.create();
try {
// Attempt to convert the date header in a variety of formats
long result = FastHttpDateFormat.parseDate(value, formats.getFormats());
if (result != (-1L)) {
return result;
}
throw new IllegalArgumentException(value);
} finally {
formats.recycle();
}
}
/**
* Return the first value of the specified header, if any; otherwise,
* return null
*
* @param name Name of the requested header
*/
public String getHeader(String name) {
return request.getHeader(name);
}
/**
* Return the first value of the specified header, if any; otherwise,
* return null
*
* @param header the requested {@link Header}
*
* @since 2.1.2
*/
public String getHeader(final Header header) {
return request.getHeader(header);
}
/**
* Return all of the values of the specified header, if any; otherwise,
* return an empty enumeration.
*
* @param name Name of the requested header
*/
public Iterable getHeaders(String name) {
return request.getHeaders().values(name);
}
/**
* Return all of the values of the specified header, if any; otherwise,
* return an empty enumeration.
*
* @param header the requested {@link Header}
*
* @since 2.1.2
*/
public Iterable getHeaders(final Header header) {
return request.getHeaders().values(header);
}
/**
* Return the names of all headers received with this request.
*/
public Iterable getHeaderNames() {
return request.getHeaders().names();
}
/**
* Return the value of the specified header as an integer, or -1 if there
* is no such header for this request.
*
* @param name Name of the requested header
*
* @exception IllegalArgumentException if the specified header value
* cannot be converted to an integer
*/
public int getIntHeader(String name) {
String value = getHeader(name);
if (value == null) {
return -1;
} else {
return Integer.parseInt(value);
}
}
/**
* Return the value of the specified header as an integer, or -1 if there
* is no such header for this request.
*
* @param header the requested {@link Header}
*
* @exception IllegalArgumentException if the specified header value
* cannot be converted to an integer
*
* @since 2.1.2
*/
public int getIntHeader(final Header header) {
String value = getHeader(header);
if (value == null) {
return -1;
} else {
return Integer.parseInt(value);
}
}
/**
* Return the HTTP request method used in this Request.
*/
public Method getMethod() {
return request.getMethod();
}
/**
* Sets the HTTP request method used in this Request.
* @param method the HTTP request method used in this Request.
*/
public void setMethod(String method) {
request.setMethod(method);
}
/**
* @return the query string associated with this request.
*/
public String getQueryString() {
final String queryString = request.getQueryStringDC().toString(
parameters.getQueryStringEncoding());
return queryString == null || queryString.isEmpty()
? null
: queryString;
}
/**
* Sets the query string associated with this request.
* @param queryString the query string associated with this request.
*/
public void setQueryString(String queryString) {
request.setQueryString(queryString);
}
/**
* Return the name of the remote user that has been authenticated
* for this Request.
*/
public String getRemoteUser() {
if (userPrincipal != null) {
return userPrincipal.getName();
} else {
return null;
}
}
/**
* Return the session identifier included in this request, if any.
*/
public String getRequestedSessionId() {
return requestedSessionId;
}
/**
* Return the request URI for this request.
*/
public String getRequestURI() {
return request.getRequestURI();
}
/**
* Sets the request URI for this request.
* @param uri the request URI for this request.
*/
public void setRequestURI(String uri) {
request.setRequestURI(uri);
}
/**
* Reconstructs the URL the client used to make the request.
* The returned URL contains a protocol, server name, port
* number, and server path, but it does not include query
* string parameters.
*
* Because this method returns a StringBuilder
,
* not a String
, you can modify the URL easily,
* for example, to append query parameters.
*
* This method is useful for creating redirect messages and
* for reporting errors.
*
* @return A StringBuffer
object containing the
* reconstructed URL
*/
public StringBuilder getRequestURL() {
final StringBuilder url = new StringBuilder();
return appendRequestURL(this, url);
}
/**
* Appends the reconstructed URL the client used to make the request.
* The appended URL contains a protocol, server name, port
* number, and server path, but it does not include query
* string parameters.
*
* Because this method returns a StringBuilder
,
* not a String
, you can modify the URL easily,
* for example, to append query parameters.
*
* This method is useful for creating redirect messages and
* for reporting errors.
*
* @return A StringBuilder
object containing the appended
* reconstructed URL
*/
public static StringBuilder appendRequestURL(final Request request,
final StringBuilder buffer) {
final String scheme = request.getScheme();
int port = request.getServerPort();
if (port < 0)
port = 80; // Work around java.net.URL bug
buffer.append(scheme);
buffer.append("://");
buffer.append(request.getServerName());
if ((scheme.equals("http") && (port != 80))
|| (scheme.equals("https") && (port != 443))) {
buffer.append(':');
buffer.append(port);
}
buffer.append(request.getRequestURI());
return buffer;
}
/**
* Appends the reconstructed URL the client used to make the request.
* The appended URL contains a protocol, server name, port
* number, and server path, but it does not include query
* string parameters.
*
* Because this method returns a StringBuffer
,
* not a String
, you can modify the URL easily,
* for example, to append query parameters.
*
* This method is useful for creating redirect messages and
* for reporting errors.
*
* @return A StringBuffer
object containing the appended
* reconstructed URL
*/
public static StringBuffer appendRequestURL(final Request request,
final StringBuffer buffer) {
final String scheme = request.getScheme();
int port = request.getServerPort();
if (port < 0)
port = 80; // Work around java.net.URL bug
buffer.append(scheme);
buffer.append("://");
buffer.append(request.getServerName());
if ((scheme.equals("http") && (port != 80))
|| (scheme.equals("https") && (port != 443))) {
buffer.append(':');
buffer.append(port);
}
buffer.append(request.getRequestURI());
return buffer;
}
/**
* Return the principal that has been authenticated for this Request.
*/
public Principal getUserPrincipal() {
if (userPrincipal == null) {
if (getRequest().isSecure()) {
X509Certificate certs[] = (X509Certificate[]) getAttribute(
Globals.CERTIFICATES_ATTR);
if (FORCE_CLIENT_AUTH_ON_GET_USER_PRINCIPAL &&
((certs == null) || (certs.length < 1))) {
// Force SSL re-handshake and request client auth
certs = (X509Certificate[]) getAttribute(
Globals.SSL_CERTIFICATE_ATTR);
}
if (certs != null && certs.length > 0) {
userPrincipal = certs[0].getSubjectX500Principal();
}
}
}
return userPrincipal;
}
public FilterChainContext getContext() {
return ctx;
}
protected String unescape(String s) {
if (s == null) {
return null;
}
if (s.indexOf('\\') == -1) {
return s;
}
StringBuilder buf = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != '\\') {
buf.append(c);
} else {
if (++i >= s.length()) {
//invalid escape, hence invalid cookie
throw new IllegalArgumentException();
}
c = s.charAt(i);
buf.append(c);
}
}
return buf.toString();
}
/**
* Parse cookies.
*/
protected void parseCookies() {
cookiesParsed = true;
final Cookies serverCookies = getRawCookies();
cookies = serverCookies.get();
}
/**
* @return the {@link InputBuffer} associated with this request, which is the
* source for {@link #getInputStream()}, {@link #getReader()},
* {@link #getNIOInputStream()}, and {@link #getNIOReader()}
*/
public InputBuffer getInputBuffer() {
return inputBuffer;
}
/**
* This method may be used if some other entity processed request parameters
* and wishes to expose them via the request. When this method is called,
* it will mark the internal request parameter state as having been processed.
*
* @param parameters the parameters to expose via this request.
*
* @since 2.2
*/
public void setRequestParameters(final Parameters parameters) {
this.requestParametersParsed = true;
for (final String name : parameters.getParameterNames()) {
this.parameters.addParameterValues(name,
parameters.getParameterValues(name));
}
}
/**
* TODO DOCS
*/
protected Cookies getRawCookies() {
if (rawCookies == null) {
rawCookies = new Cookies();
}
if (!rawCookies.initialized()) {
rawCookies.setHeaders(request.getHeaders());
}
return rawCookies;
}
/**
* Parse request parameters.
*/
protected void parseRequestParameters() {
// Delay updating requestParametersParsed to TRUE until
// after getCharacterEncoding() has been called, because
// getCharacterEncoding() may cause setCharacterEncoding() to be
// called, and the latter will ignore the specified encoding if
// requestParametersParsed is TRUE
requestParametersParsed = true;
Charset charset = null;
if (parameters.getEncoding() == null) {
// getCharacterEncoding() may have been overridden to search for
// hidden form field containing request encoding
charset = lookupCharset(getCharacterEncoding());
parameters.setEncoding(charset);
}
if (parameters.getQueryStringEncoding() == null) {
if (charset == null) {
// getCharacterEncoding() may have been overridden to search for
// hidden form field containing request encoding
charset = lookupCharset(getCharacterEncoding());
}
parameters.setQueryStringEncoding(charset);
}
parameters.handleQueryParameters();
if (usingInputStream || usingReader) {
return;
}
if (!Method.POST.equals(getMethod())) {
return;
}
if (!checkPostContentType(getContentType())) return;
final int maxFormPostSize =
httpServerFilter.getConfiguration().getMaxFormPostSize();
int len = getContentLength();
if (len < 0) {
if (!request.isChunked()) {
return;
}
len = maxFormPostSize;
}
if ((maxFormPostSize > 0) && (len > maxFormPostSize)) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.warning(LogMessages.WARNING_GRIZZLY_HTTP_SERVER_REQUEST_POST_TOO_LARGE());
}
throw new IllegalStateException(LogMessages.WARNING_GRIZZLY_HTTP_SERVER_REQUEST_POST_TOO_LARGE());
}
int read = 0;
try {
final Buffer formData = getPostBody(len);
read = formData.remaining();
parameters.processParameters(formData, formData.position(), read);
} catch (Exception ignored) {
} finally {
try {
skipPostBody(read);
} catch (Exception e) {
LOGGER.log(Level.WARNING,
LogMessages.WARNING_GRIZZLY_HTTP_SERVER_REQUEST_BODY_SKIP(), e);
}
}
}
private Charset lookupCharset(final String enc) {
Charset charset;
if (enc != null) {
try {
charset = Charsets.lookupCharset(enc);
} catch (Exception e) {
charset = com.pdd.pop.ext.glassfish.grizzly.http.util.Constants.DEFAULT_HTTP_CHARSET;
}
} else {
charset = com.pdd.pop.ext.glassfish.grizzly.http.util.Constants.DEFAULT_HTTP_CHARSET;
}
return charset;
}
private boolean checkPostContentType(final String contentType) {
return ((contentType != null)
&& contentType.trim().startsWith(FORM_POST_CONTENT_TYPE));
}
/**
* Gets the POST body of this request.
*
* @return The POST body of this request
*/
public Buffer getPostBody(final int len) throws IOException {
inputBuffer.fillFully(len);
return inputBuffer.getBuffer();
}
/**
* Skips the POST body of this request.
*
* @param len how much of the POST body to skip.
*/
protected void skipPostBody(final int len) throws IOException {
inputBuffer.skip(len);
}
/**
* Parse request locales.
*/
protected void parseLocales() {
localesParsed = true;
final Iterable values = getHeaders("accept-language");
for (String value : values) {
parseLocalesHeader(value);
}
}
/**
* Parse accept-language header value.
*/
protected void parseLocalesHeader(String value) {
// Store the accumulated languages that have been requested in
// a local collection, sorted by the quality value (so we can
// add Locales in descending order). The values will be ArrayLists
// containing the corresponding Locales to be added
TreeMap> localLocalesMap = new TreeMap>();
// Preprocess the value to remove all whitespace
int white = value.indexOf(' ');
if (white < 0)
white = value.indexOf('\t');
if (white >= 0) {
int len = value.length();
StringBuilder sb = new StringBuilder(len - 1);
for (int i = 0; i < len; i++) {
char ch = value.charAt(i);
if ((ch != ' ') && (ch != '\t'))
sb.append(ch);
}
value = sb.toString();
}
if (parser == null) {
parser = new StringParser();
}
// Process each comma-delimited language specification
parser.setString(value); // ASSERT: parser is available to us
int length = parser.getLength();
while (true) {
// Extract the next comma-delimited entry
int start = parser.getIndex();
if (start >= length)
break;
int end = parser.findChar(',');
String entry = parser.extract(start, end).trim();
parser.advance(); // For the following entry
// Extract the quality factor for this entry
double quality = 1.0;
int semi = entry.indexOf(";q=");
if (semi >= 0) {
final String qvalue = entry.substring(semi + 3);
// qvalues, according to the RFC, may not contain more
// than three values after the decimal.
if (qvalue.length() <= 5) {
try {
quality = Double.parseDouble(qvalue);
} catch (NumberFormatException e) {
quality = 0.0;
}
} else {
quality = 0.0;
}
entry = entry.substring(0, semi);
}
// Skip entries we are not going to keep track of
if (quality < 0.00005)
continue; // Zero (or effectively zero) quality factors
if ("*".equals(entry))
continue; // FIXME - "*" entries are not handled
Locale locale = localeParser.parseLocale(entry);
if (locale == null) {
continue;
}
// Add a new Locale to the list of Locales for this quality level
Double key = -quality; // Reverse the order
List values = localLocalesMap.get(key);
if (values == null) {
values = new ArrayList();
localLocalesMap.put(key, values);
}
values.add(locale);
}
// Process the quality values in highest->lowest order (due to
// negating the Double value when creating the key)
for (List localLocales: localLocalesMap.values()) {
for (Locale locale : localLocales) {
addLocale(locale);
}
}
}
/**
* Parses the value of the JROUTE cookie, if present.
*/
void parseJrouteCookie() {
if (!cookiesParsed) {
parseCookies();
}
final Cookie cookie = getRawCookies().findByName(Constants.JROUTE_COOKIE);
if (cookie != null) {
setJrouteId(cookie.getValue());
}
}
/*
* @return true
if the given string is composed of
* upper- or lowercase letters only, false
otherwise.
*/
static boolean isAlpha(String value) {
if (value == null) {
return false;
}
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
return false;
}
}
return true;
}
/**
* Sets the jroute id of this request.
*
* @param jrouteId The jroute id
*/
void setJrouteId(String jrouteId) {
this.jrouteId = jrouteId;
}
/**
* Gets the jroute id of this request, which may have been
* sent as a separate JROUTE
cookie or appended to the
* session identifier encoded in the URI (if cookies have been disabled).
*
* @return The jroute id of this request, or null if this request does not
* carry any jroute id
*/
public String getJrouteId() {
return jrouteId;
}
// ------------------------------------------------------ Session support --/
/**
* Return the session associated with this Request, creating one
* if necessary.
*/
public Session getSession() {
return doGetSession(true);
}
/**
* Return the session associated with this Request, creating one
* if necessary and requested.
*
* @param create Create a new session if one does not exist
*/
public Session getSession(final boolean create) {
return doGetSession(create);
}
/**
* Change the session id of the current session associated with this
* request and return the new session id.
*
* @return the original session id
*
* @throws IllegalStateException if there is no session associated
* with the request
*
* @since 2.3
*/
public String changeSessionId() {
Session sessionLocal = doGetSession(false);
if (sessionLocal == null) {
throw new IllegalStateException("changeSessionId has been called without a session");
}
String oldSessionId = getSessionManager().changeSessionId(this, sessionLocal);
final String newSessionId = sessionLocal.getIdInternal();
requestedSessionId = newSessionId;
if (isRequestedSessionIdFromURL())
return oldSessionId;
if (response != null) {
final Cookie cookie = new Cookie(obtainSessionCookieName(),
newSessionId);
configureSessionCookie(cookie);
response.addSessionCookieInternal(cookie);
}
return oldSessionId;
}
protected Session doGetSession(final boolean create) {
// Return the current session if it exists and is valid
if (session != null && session.isValid()) {
return session;
}
session = null;
if (requestedSessionId == null) {
final Cookie[] cookiesLocale = getCookies();
assert cookiesLocale != null;
final String sessionCookieNameLocal = obtainSessionCookieName();
for (int i = 0; i < cookiesLocale.length; i++) {
final Cookie c = cookiesLocale[i];
if (sessionCookieNameLocal.equals(c.getName())) {
setRequestedSessionId(c.getValue());
setRequestedSessionCookie(true);
break;
}
}
}
session = getSessionManager().getSession(this, requestedSessionId);
if (session != null && !session.isValid()) {
session = null;
}
if (session != null) {
session.access();
return session;
}
if (!create) {
return null;
}
session = getSessionManager().createSession(this);
session.setSessionTimeout(
httpServerFilter.getConfiguration().getSessionTimeoutSeconds() * 1000);
requestedSessionId = session.getIdInternal();
// Creating a new session cookie based on the newly created session
final Cookie cookie = new Cookie(obtainSessionCookieName(),
session.getIdInternal());
configureSessionCookie(cookie);
response.addCookie(cookie);
return session;
}
/**
* @return true
if the session identifier included in this
* request came from a cookie.
*/
public boolean isRequestedSessionIdFromCookie() {
return requestedSessionId != null && requestedSessionCookie;
}
/**
* Return true
if the session identifier included in this
* request came from the request URI.
*/
public boolean isRequestedSessionIdFromURL() {
return requestedSessionId != null && requestedSessionURL;
}
/**
* @return true if the session identifier included in this
* request identifies a valid session.
*/
public boolean isRequestedSessionIdValid() {
if (requestedSessionId == null) {
return false;
}
if (session != null
&& requestedSessionId.equals(session.getIdInternal())) {
return session.isValid();
}
final Session localSession =
getSessionManager().getSession(this, requestedSessionId);
return localSession != null && localSession.isValid();
}
/**
* Configures the given session cookie.
*
* @param cookie The session cookie to be configured
*/
protected void configureSessionCookie(final Cookie cookie) {
cookie.setMaxAge(-1);
cookie.setPath("/");
if (isSecure()) {
cookie.setSecure(true);
}
getSessionManager().configureSessionCookie(this, cookie);
}
/**
* Parse session id in URL.
*/
protected void parseSessionId() {
if (sessionParsed) return;
sessionParsed = true;
final DataChunk uriDC = request.getRequestURIRef().getRequestURIBC();
final boolean isUpdated;
switch (uriDC.getType()) {
case Bytes:
isUpdated = parseSessionId(uriDC.getByteChunk());
break;
case Buffer:
isUpdated = parseSessionId(uriDC.getBufferChunk());
break;
case Chars:
isUpdated = parseSessionId(uriDC.getCharChunk());
break;
case String:
isUpdated = parseSessionId(uriDC);
break;
default:
throw new IllegalStateException("Unexpected DataChunk type: " + uriDC.getType());
}
if (isUpdated) {
uriDC.notifyDirectUpdate();
}
}
private boolean parseSessionId(final Chunk uriChunk) {
final String sessionParamNameMatch = sessionCookieName != null
? ';' + sessionCookieName + '='
: match;
boolean isUpdated = false;
final int semicolon = uriChunk.indexOf(sessionParamNameMatch, 0);
if (semicolon > 0) {
// Parse session ID, and extract it from the decoded request URI
// final int start = uriChunk.getStart();
final int sessionIdStart = semicolon + sessionParamNameMatch.length();
final int semicolon2 = uriChunk.indexOf(';', sessionIdStart);
isUpdated = semicolon2 >= 0;
final int end = isUpdated ? semicolon2 : uriChunk.getLength();
final String sessionId = uriChunk.toString(sessionIdStart, end);
final int jrouteIndex = sessionId.lastIndexOf(':');
if (jrouteIndex > 0) {
setRequestedSessionId(sessionId.substring(0, jrouteIndex));
if (jrouteIndex < (sessionId.length() - 1)) {
setJrouteId(sessionId.substring(jrouteIndex + 1));
}
} else {
setRequestedSessionId(sessionId);
}
setRequestedSessionURL(true);
uriChunk.delete(semicolon, end);
} else {
setRequestedSessionId(null);
setRequestedSessionURL(false);
}
return isUpdated;
}
private boolean parseSessionId(DataChunk dataChunkStr) {
assert dataChunkStr.getType() == DataChunk.Type.String;
final String uri = dataChunkStr.toString();
final String sessionParamNameMatch = sessionCookieName != null
? ';' + sessionCookieName + '='
: match;
boolean isUpdated = false;
final int semicolon = uri.indexOf(sessionParamNameMatch);
if (semicolon > 0) {
// Parse session ID, and extract it from the decoded request URI
// final int start = uriChunk.getStart();
final int sessionIdStart = semicolon + sessionParamNameMatch.length();
final int semicolon2 = uri.indexOf(';', sessionIdStart);
isUpdated = semicolon2 >= 0;
final int end = isUpdated ? semicolon2 : uri.length();
final String sessionId = uri.substring(sessionIdStart, end);
final int jrouteIndex = sessionId.lastIndexOf(':');
if (jrouteIndex > 0) {
setRequestedSessionId(sessionId.substring(0, jrouteIndex));
if (jrouteIndex < (sessionId.length() - 1)) {
setJrouteId(sessionId.substring(jrouteIndex + 1));
}
} else {
setRequestedSessionId(sessionId);
}
setRequestedSessionURL(true);
dataChunkStr.setString(uri.substring(0, semicolon));
} else {
setRequestedSessionId(null);
setRequestedSessionURL(false);
}
return isUpdated;
}
/**
* Set a flag indicating whether or not the requested session ID for this
* request came in through a cookie. This is normally called by the
* HTTP Connector, when it parses the request headers.
*
* @param flag The new flag
*/
public void setRequestedSessionCookie(boolean flag) {
this.requestedSessionCookie = flag;
}
/**
* Set the requested session ID for this request. This is normally called
* by the HTTP Connector, when it parses the request headers.
*
* @param id The new session id
*/
public void setRequestedSessionId(String id) {
this.requestedSessionId = id;
}
/**
* Set a flag indicating whether or not the requested session ID for this
* request came in through a URL. This is normally called by the
* HTTP Connector, when it parses the request headers.
*
* @param flag The new flag
*/
public void setRequestedSessionURL(boolean flag) {
this.requestedSessionURL = flag;
}
private static class PathData {
private final Request request;
private String path;
private PathResolver resolver;
public PathData(Request request) {
this.request = request;
}
public PathData(final Request request, final String path,
final PathResolver resolver) {
this.request = request;
this.path = path;
this.resolver = resolver;
}
public void setPath(final String path) {
this.path = path;
resolver = null;
}
public void setResolver(final PathResolver resolver) {
this.resolver = resolver;
path = null;
}
public String get() {
return path != null ? path :
(resolver != null ? (path = resolver.resolve(request)) : null);
}
public void reset() {
path = null;
resolver = null;
}
}
protected interface PathResolver {
String resolve(Request request);
}
}