| /* |
| * 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.asyncweb.common; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.net.URI; |
| import java.net.URLDecoder; |
| import java.net.URLEncoder; |
| import java.nio.charset.CharacterCodingException; |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.TimeZone; |
| |
| import org.apache.asyncweb.common.codec.HttpCodecUtils; |
| import org.apache.mina.core.buffer.IoBuffer; |
| |
| /** |
| * A default implementation of {@link MutableHttpRequest}. |
| * |
| * @author The Apache MINA Project (dev@mina.apache.org) |
| */ |
| public class DefaultHttpRequest extends DefaultHttpMessage implements |
| MutableHttpRequest { |
| |
| private static final long serialVersionUID = 3044997961372568928L; |
| |
| private HttpMethod method = HttpMethod.GET; |
| private URI requestUri; |
| private Map<String, List<String>> parameters = new HashMap<String, List<String>>(); |
| |
| /** |
| * Creates a new instance. |
| */ |
| public DefaultHttpRequest() { |
| } |
| |
| public void setCookies(String headerValue) { |
| clearCookies(); |
| |
| int version = -1; // -1 means version is not parsed yet. |
| int fieldIdx = 0; |
| MutableCookie currentCookie = null; |
| |
| StringTokenizer tk = new StringTokenizer(headerValue, ";,"); |
| |
| while (tk.hasMoreTokens()) { |
| String pair = tk.nextToken(); |
| String key; |
| String value; |
| |
| int equalsPos = pair.indexOf('='); |
| if (equalsPos >= 0) { |
| key = pair.substring(0, equalsPos).trim(); |
| value = pair.substring(equalsPos + 1).trim(); |
| } else { |
| key = pair.trim(); |
| value = ""; |
| } |
| |
| if (version < 0) { |
| if (!key.equalsIgnoreCase("$Version")) { |
| // $Version is not specified. Use the default (0). |
| version = 0; |
| } else { |
| version = Integer.parseInt(value); |
| if (version != 0 && version != 1) { |
| throw new IllegalArgumentException("Invalid version: " |
| + version + " (" + headerValue + ")"); |
| } |
| } |
| } |
| |
| if (version >= 0) { |
| try { |
| switch (fieldIdx) { |
| case 1: |
| if (key.equalsIgnoreCase("$Path")) { |
| currentCookie.setPath(value); |
| fieldIdx++; |
| } else { |
| fieldIdx = 0; |
| } |
| break; |
| case 2: |
| if (key.equalsIgnoreCase("$Domain")) { |
| currentCookie.setDomain(value); |
| fieldIdx++; |
| } else { |
| fieldIdx = 0; |
| } |
| break; |
| case 3: |
| if (key.equalsIgnoreCase("$Port")) { |
| // Ignoring for now |
| fieldIdx++; |
| } else { |
| fieldIdx = 0; |
| } |
| break; |
| } |
| } catch (NullPointerException e) { |
| throw new IllegalArgumentException( |
| "Cookie key-value pair not found (" + headerValue |
| + ")"); |
| } |
| |
| if (fieldIdx == 0) { |
| currentCookie = new DefaultCookie(key); |
| currentCookie.setVersion(version); |
| currentCookie.setValue(value); |
| addCookie(currentCookie); |
| fieldIdx++; |
| } |
| } |
| } |
| } |
| |
| public URI getRequestUri() { |
| return requestUri; |
| } |
| |
| public void setRequestUri(URI requestUri) { |
| if (requestUri == null) { |
| throw new NullPointerException("requestUri"); |
| } |
| this.requestUri = requestUri; |
| } |
| |
| public boolean requiresContinuationResponse() { |
| if (getProtocolVersion() == HttpVersion.HTTP_1_1) { |
| String expectations = getHeader(HttpHeaderConstants.KEY_EXPECT); |
| if (expectations != null) { |
| return expectations |
| .indexOf(HttpHeaderConstants.VALUE_CONTINUE_EXPECTATION) >= 0; |
| } |
| } |
| return false; |
| } |
| |
| public HttpMethod getMethod() { |
| return method; |
| } |
| |
| public void setMethod(HttpMethod method) { |
| if (method == null) { |
| throw new NullPointerException("method"); |
| } |
| this.method = method; |
| } |
| |
| public void addParameter(String name, String value) { |
| List<String> values = parameters.get(name); |
| if (values == null) { |
| values = new ArrayList<String>(); |
| parameters.put(name, values); |
| } |
| values.add(value); |
| } |
| |
| public boolean removeParameter(String name) { |
| return parameters.remove(name) != null; |
| } |
| |
| public void setParameter(String name, String value) { |
| List<String> values = new ArrayList<String>(); |
| values.add(value); |
| parameters.put(name, values); |
| } |
| |
| public void setParameters(Map<String, List<String>> parameters) { |
| clearParameters(); |
| |
| for (Map.Entry<String, List<String>> entry : parameters.entrySet()) { |
| for (String value : entry.getValue()) { |
| if (value == null) { |
| throw new NullPointerException("Parameter '" |
| + entry.getKey() + "' contains null."); |
| } |
| } |
| if (entry.getValue().size() > 0) { |
| this.parameters.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| } |
| |
| public void setParameters(String queryString) { |
| try { |
| this.setParameters(queryString, HttpCodecUtils.DEFAULT_CHARSET_NAME); |
| } catch (UnsupportedEncodingException e) { |
| throw new InternalError( |
| HttpCodecUtils.DEFAULT_CHARSET_NAME + |
| " decoder must be provided by JDK."); |
| } |
| } |
| |
| public void setParameters(String queryString, String encoding) |
| throws UnsupportedEncodingException { |
| clearParameters(); |
| |
| if (queryString == null || queryString.length() == 0) { |
| return; |
| } |
| |
| int pos = 0; |
| while (pos < queryString.length()) { |
| int ampPos = queryString.indexOf('&', pos); |
| |
| String value; |
| if (ampPos < 0) { |
| value = queryString.substring(pos); |
| ampPos = queryString.length(); |
| } else { |
| value = queryString.substring(pos, ampPos); |
| } |
| |
| int equalPos = value.indexOf('='); |
| if (equalPos < 0) { |
| this.addParameter(URLDecoder.decode(value, encoding), ""); |
| } else { |
| this.addParameter(URLDecoder.decode(value |
| .substring(0, equalPos), encoding), URLDecoder.decode( |
| value.substring(equalPos + 1), encoding)); |
| } |
| |
| pos = ampPos + 1; |
| } |
| } |
| |
| public void clearParameters() { |
| this.parameters.clear(); |
| } |
| |
| public boolean containsParameter(String name) { |
| return parameters.containsKey(name); |
| } |
| |
| public String getParameter(String name) { |
| List<String> values = parameters.get(name); |
| if (values == null) { |
| return null; |
| } |
| |
| return values.get(0); |
| } |
| |
| public Map<String, List<String>> getParameters() { |
| return Collections.unmodifiableMap(parameters); |
| } |
| |
| @Override |
| public void setContent(IoBuffer content) { |
| if (content == null) { |
| throw new NullPointerException("content"); |
| } |
| |
| String ct = getContentType(); |
| if (ct != null && ct.toLowerCase().startsWith(HttpHeaderConstants.VALUE_URLENCODED_FORM.toLowerCase())) { |
| content.mark(); |
| try { |
| setParameters(content.getString( |
| HttpCodecUtils.DEFAULT_CHARSET.newDecoder())); |
| } catch (CharacterCodingException e) { |
| throw new IllegalArgumentException( |
| "Failed to decode the url-encoded content.", e); |
| } finally { |
| content.reset(); |
| } |
| } |
| super.setContent(content); |
| } |
| |
| /** |
| * Thread-local DateFormat for old-style cookies |
| */ |
| private static final ThreadLocal<DateFormat> EXPIRY_FORMAT_LOACAL = new ThreadLocal<DateFormat>() { |
| @Override |
| protected DateFormat initialValue() { |
| SimpleDateFormat format = new SimpleDateFormat( |
| "EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US); |
| format.setTimeZone(TimeZone.getTimeZone( |
| HttpCodecUtils.DEFAULT_TIME_ZONE_NAME)); |
| return format; |
| } |
| |
| }; |
| |
| /** |
| * A date long long ago, formatted in the old style cookie expire format |
| */ |
| private static final String EXPIRED_DATE = getFormattedExpiry(0); |
| |
| private static String getFormattedExpiry(long time) { |
| DateFormat format = EXPIRY_FORMAT_LOACAL.get(); |
| return format.format(new Date(time)); |
| } |
| |
| public void normalize() { |
| // Encode parameters. |
| Map<String, List<String>> params = getParameters(); |
| if (!params.isEmpty()) { |
| try { |
| boolean first = true; |
| StringBuilder buf = new StringBuilder(); |
| for (Map.Entry<String, List<String>> e: params.entrySet()) { |
| if (e.getValue().isEmpty()) { |
| continue; |
| } |
| |
| for (String v: e.getValue()) { |
| if (!first) { |
| buf.append('&'); |
| } |
| |
| buf.append(URLEncoder.encode( |
| e.getKey(), HttpCodecUtils.DEFAULT_CHARSET_NAME)); |
| buf.append('='); |
| buf.append(URLEncoder.encode( |
| v, HttpCodecUtils.DEFAULT_CHARSET_NAME)); |
| first = false; |
| } |
| } |
| |
| if (buf.length() > 0) { |
| String uri = getRequestUri().toString(); |
| int queryIndex = uri.indexOf('?'); |
| switch (getMethod()) { |
| case POST: |
| if (queryIndex >= 0) { |
| setRequestUri(new URI(uri.substring(0, queryIndex))); |
| } |
| IoBuffer content = IoBuffer.allocate(buf.length()); |
| content.put(buf.toString().getBytes(HttpCodecUtils.US_ASCII_CHARSET_NAME)); |
| content.flip(); |
| setContent(content); |
| setHeader( |
| HttpHeaderConstants.KEY_CONTENT_TYPE, |
| HttpHeaderConstants.VALUE_URLENCODED_FORM); |
| break; |
| default: |
| if (queryIndex >= 0) { |
| setRequestUri(new URI( |
| uri.substring(0, queryIndex + 1) + buf)); |
| } else { |
| setRequestUri(new URI( |
| uri + '?' + buf)); |
| } |
| } |
| } |
| } catch (Exception e) { |
| throw (InternalError) new InternalError( |
| "Unexpected exception.").initCause(e); |
| } |
| } |
| |
| // Encode Cookies |
| Set<Cookie> cookies = getCookies(); |
| if (!cookies.isEmpty()) { |
| // Clear previous values. |
| removeHeader(HttpHeaderConstants.KEY_COOKIE); |
| |
| // And encode. |
| for (Cookie c: cookies) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append(c.getName()); |
| buf.append('='); |
| buf.append(c.getValue()); |
| if (c.getVersion() > 0) { |
| buf.append("; version="); |
| buf.append(c.getVersion()); |
| } |
| if (c.getPath() != null) { |
| buf.append("; path="); |
| buf.append(c.getPath()); |
| } |
| |
| long expiry = c.getMaxAge(); |
| int version = c.getVersion(); |
| if (expiry >= 0) { |
| if (version == 0) { |
| String expires = expiry == 0 ? EXPIRED_DATE |
| : getFormattedExpiry(System.currentTimeMillis() |
| + 1000 * expiry); |
| buf.append("; Expires="); |
| buf.append(expires); |
| } else { |
| buf.append("; max-age="); |
| buf.append(c.getMaxAge()); |
| } |
| } |
| |
| if (c.isSecure()) { |
| buf.append("; secure"); |
| } |
| if (c.isHttpOnly()) { |
| buf.append("; HTTPOnly"); |
| } |
| |
| buf.append(';'); |
| |
| addHeader( HttpHeaderConstants.KEY_COOKIE, buf.toString() ); |
| } |
| } |
| |
| // Add the Host header. |
| if (!containsHeader(HttpHeaderConstants.KEY_HOST)) { |
| URI uri = getRequestUri(); |
| String host = uri.getHost(); |
| if (host != null) { |
| if ((uri.getScheme().equalsIgnoreCase("http") && |
| uri.getPort() != 80 && uri.getPort() > 0) || |
| (uri.getScheme().equalsIgnoreCase("https") && |
| uri.getPort() != 443 && uri.getPort() > 0)) { |
| setHeader(HttpHeaderConstants.KEY_HOST, host + ':' + uri.getPort()); |
| } else { |
| setHeader(HttpHeaderConstants.KEY_HOST, host); |
| } |
| } |
| } |
| |
| // Set Content-Length. |
| if (!containsHeader(HttpHeaderConstants.KEY_TRANSFER_CODING)) { |
| IoBuffer content = getContent(); |
| int contentLength = content == null? 0 : content.remaining(); |
| setHeader( |
| HttpHeaderConstants.KEY_CONTENT_LENGTH, |
| String.valueOf(contentLength)); |
| } |
| } |
| } |