| /* |
| * 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) |
| * @version $Rev$, $Date$ |
| */ |
| 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 |
| |
| } |
| } |