blob: faf111bd0e7b077f3e2318cf4daf365019f93a2c [file] [log] [blame]
/*
* 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.codec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.List;
import java.util.Map;
import org.apache.asyncweb.common.HttpMessage;
import org.apache.asyncweb.common.HttpResponseStatus;
import org.apache.asyncweb.common.HttpResponseStatus.Category;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.ProtocolDecoderException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author The Apache MINA Project (dev@mina.apache.org)
*/
public class HttpCodecUtils {
public static final String DEFAULT_CHARSET_NAME = "UTF-8";
public static final Charset DEFAULT_CHARSET =
Charset.forName(DEFAULT_CHARSET_NAME);
public static final String US_ASCII_CHARSET_NAME = "US-ASCII";
public static final Charset US_ASCII_CHARSET =
Charset.forName(US_ASCII_CHARSET_NAME);
public static final String DEFAULT_TIME_ZONE_NAME = "GMT";
/**
* Bytes making up a <code>CR LF</code>
*/
private static final byte[] CRLF_BYTES = new byte[] { '\r', '\n' };
/**
* A lookup table for HTPP separator characters
*/
private static boolean[] HTTP_SEPARATORS = new boolean[128];
/**
* A lookup table for HTTP control characters
*/
private static boolean[] HTTP_CONTROLS = new boolean[128];
/**
* A lookup table from ASCII char values to corresponding decimal values
*/
private static final int[] HEX_DEC = { -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, 00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1,
-1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, };
private static final Logger LOG = LoggerFactory
.getLogger(HttpCodecUtils.class);
/**
* Determines whether a specified (US-ASCII) character is a
* valid hex character:
* <ul>
* <li>A..F</li>
* <li>a..f</li>
* <li>0..9</li>
* </ul>
*
* @param b The character to check
* @return <code>true</code> iff the character is a valid hex
* character
*/
static boolean isHex(byte b) {
return HEX_DEC[b] != -1;
}
/**
* Determines whether a specified (US-ASCII) character is an
* HTTP field separator
*
* @param b the US-ASCII character to check
* @return <code>true</code> iff the character is an HTTP field
* separator
*/
public static boolean isHttpSeparator(byte b) {
return HTTP_SEPARATORS[b];
}
/**
* Determines whether a specified (US-ASCII) character is an
* HTTP control character
*
* @param b the US-ASCII character to check
* @return <code>true</code> iff the character is an HTTP control
* character
*/
public static boolean isHttpControl(byte b) {
return HTTP_CONTROLS[b];
}
/**
* Throws a {@link HttpRequestDecoderException} with a specified cause.
* This method should be used when the request is badly formed.
*
* @param cause The cause
*/
static void throwDecoderException(String cause)
throws ProtocolDecoderException {
throwDecoderException(cause, HttpResponseStatus.BAD_REQUEST);
}
/**
* Throws a {@link HttpRequestDecoderException} with a specified response
* status
*
* @param message The cause
* @param status The response status
*/
static void throwDecoderException(String message,
HttpResponseStatus status) throws ProtocolDecoderException {
if (!(status.getCategory() == HttpResponseStatus.Category.CLIENT_ERROR || status
.getCategory() == HttpResponseStatus.Category.SERVER_ERROR)) {
throw new IllegalArgumentException(
"Cant fail with successful response");
}
if (LOG.isInfoEnabled()) {
LOG.info("Decode failure. Cause: " + message);
}
throw new HttpRequestDecoderException(message, status);
}
/**
* Appends a string to a specified {@link IoBuffer}.
* This method assumes ascii encoding and is primarily used for encoding
* http header names and values.<br/>
* Note that encoding header values this way is not stricly correct
* (character encodings). However, existing containers do it this way
* (e.g. Tomcat), and we're probably safer doing it a similar way for the
* time being
*
* @param buffer The buffer to append to
* @param string The string to append
*/
static void appendString(IoBuffer buffer, String string) {
if (string == null) {
return;
}
int len = string.length();
for (int i = 0; i < len; i++) {
byte b = (byte) string.charAt(i);
if (isHttpControl(b) && b != '\t') {
b = ' ';
}
buffer.put(b);
}
}
/**
* Appends a <code>CR LF</code> to the specified buffer
*
* @param buffer The buffer
*/
static void appendCRLF(IoBuffer buffer) {
buffer.put(CRLF_BYTES);
}
/**
* Encodes the headers of a <code>Response</code> to a specified buffer.
* This encoder does not make smart decisions about which headers to write -
* the response is expected to already contain self-consistent headers.
*
* @param message The response whose headers are to be encoded
* @param buffer The buffer
*/
static void encodeHeaders(
HttpMessage message, IoBuffer buffer, CharsetEncoder encoder) throws CharacterCodingException {
try {
for (Map.Entry<String, List<String>> header : message.getHeaders()
.entrySet()) {
byte[] key = header.getKey().getBytes(
HttpCodecUtils.US_ASCII_CHARSET_NAME);
for (String value : header.getValue()) {
buffer.put(key);
buffer.put((byte) ':');
buffer.put((byte) ' ');
buffer.putString(value, encoder);
HttpCodecUtils.appendCRLF(buffer);
}
}
} catch (UnsupportedEncodingException e) {
throw new InternalError(
HttpCodecUtils.US_ASCII_CHARSET_NAME +
" should be available.");
}
HttpCodecUtils.appendCRLF(buffer);
}
/**
* Writes the response body bytes, if any, to the specified buffer
*
* @param message The response
* @param buffer The buffer to write to
*/
static void encodeBody(HttpMessage message, IoBuffer buffer) {
IoBuffer content = message.getContent();
buffer.put(content);
}
static {
// HTTP Separator characters
HTTP_SEPARATORS[34] = true; // "
HTTP_SEPARATORS[40] = true; // )
HTTP_SEPARATORS[41] = true; // (
HTTP_SEPARATORS[44] = true; // ,
HTTP_SEPARATORS[47] = true; // /
HTTP_SEPARATORS[58] = true; // :
HTTP_SEPARATORS[59] = true; // ;
HTTP_SEPARATORS[60] = true; // <
HTTP_SEPARATORS[61] = true; // =
HTTP_SEPARATORS[62] = true; // >
HTTP_SEPARATORS[63] = true; // ?
HTTP_SEPARATORS[64] = true; // @
HTTP_SEPARATORS[91] = true; // [
HTTP_SEPARATORS[93] = true; // ]
HTTP_SEPARATORS[92] = true; // \
HTTP_SEPARATORS[123] = true; // {
HTTP_SEPARATORS[125] = true; // }
// HTTP Control characters
for (int i = 0; i <= 31; ++i) {
HTTP_CONTROLS[i] = true;
}
HTTP_CONTROLS[127] = true; // DEL
}
}