View Javadoc

    1   /*******************************************************************************
    2    * Copyright (c) 2011 Michael Mimo Moratti.
    3    *
    4    * Michael Mimo Moratti licenses this file to you under the Apache License, version 2.0
    5    * (the "License"); you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at:
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    * Unless required by applicable law or agreed to in writing, software
    9    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    10   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
    11   * License for the specific language governing permissions and limitations
    12   * under the License.
    13   *******************************************************************************/
    14  package ch.mimo.netty.handler.codec.icap;
    15
    16  import java.util.ArrayList;
    17  import java.util.List;
    18
    19  import org.jboss.netty.buffer.ChannelBuffer;
    20  import org.jboss.netty.handler.codec.frame.TooLongFrameException;
    21
    22  /**
    23   * Utility that provides decoding support for ICAP messages.
    24   *
    25   * @author Michael Mimo Moratti (mimo@mimo.ch)
    26   *
    27   */
    28  public final class IcapDecoderUtil {
    29
    30  	private IcapDecoderUtil() {
    31  	}
    32
    33  	/**
    34  	 * finds the true beginning of the request
    35  	 * by skipping all prepended control and whitespace characters.
    36  	 * @param buffer
    37  	 */
    38      public static void skipControlCharacters(ChannelBuffer buffer) {
    39          for (;;) {
    40              char c = (char) buffer.readUnsignedByte();
    41              if (!Character.isISOControl(c) &&
    42                  !Character.isWhitespace(c)) {
    43                  buffer.readerIndex(buffer.readerIndex() - 1);
    44                  break;
    45              }
    46          }
    47      }
    48
    49      /**
    50       * reads a line until CR / LF / CRLF
    51       * @param buffer
    52       * @param maxLineLength
    53       * @return the first line found in the buffer.
    54       * @throws TooLongFrameException
    55       */
    56      public static String readLine(ChannelBuffer buffer, int maxLineLength) throws DecodingException {
    57          StringBuilder sb = new StringBuilder(64);
    58          int lineLength = 0;
    59          while (true) {
    60              byte nextByte = buffer.readByte();
    61              if (nextByte == IcapCodecUtil.CR) {
    62                  nextByte = buffer.readByte();
    63                  if (nextByte == IcapCodecUtil.LF) {
    64                      return sb.toString();
    65                  }
    66              } else if (nextByte == IcapCodecUtil.LF) {
    67                  return sb.toString();
    68              } else {
    69                  if (lineLength >= maxLineLength) {
    70                      throw new DecodingException(new TooLongFrameException(
    71                              "An HTTP line is larger than " + maxLineLength +
    72                              " bytes."));
    73                  }
    74                  lineLength ++;
    75                  sb.append((char) nextByte);
    76              }
    77          }
    78      }
    79
    80      /**
    81       * previews a line until CR / LF / CRLF
    82       * this will not increas the buffers readerIndex!
    83       * @param buffer
    84       * @param maxLineLength
    85       * @return the first line found in the buffer
    86       * @throws DecodingException
    87       */
    88      public static String previewLine(ChannelBuffer buffer, int maxLineLength) throws DecodingException {
    89          StringBuilder sb = new StringBuilder(64);
    90          int lineLength = 0;
    91          for(int i = buffer.readerIndex() ; i < buffer.readableBytes() ; i++) {
    92              byte nextByte = buffer.getByte(i);
    93              if (nextByte == IcapCodecUtil.CR) {
    94                  nextByte = buffer.getByte(++i);
    95                  if (nextByte == IcapCodecUtil.LF) {
    96                  	break;
    97                  }
    98              } else if (nextByte == IcapCodecUtil.LF) {
    99              	break;
    100             } else {
    101                 if (lineLength >= maxLineLength) {
    102                     throw new DecodingException(new TooLongFrameException(
    103                             "An HTTP line is larger than " + maxLineLength +
    104                             " bytes."));
    105                 }
    106                 lineLength ++;
    107                 sb.append((char) nextByte);
    108             }
    109         }
    110         return sb.toString();
    111     }
    112
    113     /**
    114      * Splits an initial line.
    115      * @param sb
    116      * @return string array containing all elements found in the initial line.
    117      */
    118 	public static String[] splitInitialLine(String sb) {
    119 		int aStart;
    120 		int aEnd;
    121 		int bStart;
    122 		int bEnd;
    123 		int cStart;
    124 		int cEnd;
    125
    126 		aStart = findNonWhitespace(sb, 0);
    127 		aEnd = findWhitespace(sb, aStart);
    128
    129 		bStart = findNonWhitespace(sb, aEnd);
    130 		bEnd = findWhitespace(sb, bStart);
    131
    132 		cStart = findNonWhitespace(sb, bEnd);
    133 		cEnd = findEndOfString(sb);
    134
    135 		return new String[] { sb.substring(aStart, aEnd),
    136 				sb.substring(bStart, bEnd),
    137 				cStart < cEnd ? sb.substring(cStart, cEnd) : "" };
    138 	}
    139
    140 	/**
    141 	 * finds the first occurrence of a non whitespace character.
    142 	 * @param sb string to find non-whitespaces in
    143 	 * @param offset the offset to start searching from.
    144 	 * @return the position of the first non-whitespace
    145 	 */
    146 	public static int findNonWhitespace(String sb, int offset) {
    147 		int result;
    148 		for (result = offset; result < sb.length(); result++) {
    149 			if (!Character.isWhitespace(sb.charAt(result))) {
    150 				break;
    151 			}
    152 		}
    153 		return result;
    154 	}
    155
    156 	/**
    157 	 * finds the first occurrence of a whitespace character
    158 	 * @param sb string to find whitespaces in.
    159 	 * @param offset to search from within the string.
    160 	 * @return the position of the first whitespace.
    161 	 */
    162 	public static int findWhitespace(String sb, int offset) {
    163 		int result;
    164 		for (result = offset; result < sb.length(); result++) {
    165 			if (Character.isWhitespace(sb.charAt(result))) {
    166 				break;
    167 			}
    168 		}
    169 		return result;
    170 	}
    171
    172 	/**
    173 	 * finds the end of a string.
    174 	 * @param sb string to find the end from
    175 	 * @return the end position.
    176 	 */
    177 	public static int findEndOfString(String sb) {
    178 		int result;
    179 		for (result = sb.length(); result > 0; result--) {
    180 			if (!Character.isWhitespace(sb.charAt(result - 1))) {
    181 				break;
    182 			}
    183 		}
    184 		return result;
    185 	}
    186
    187 	/**
    188 	 * parses the chunk size from a line.
    189 	 *
    190 	 * @param line
    191 	 * @return -1 if the chunk size indicates that a preview message is early terminated.
    192 	 */
    193     public static int getChunkSize(String line) throws DecodingException {
    194         String hex = line.trim();
    195         if(hex.equals(IcapCodecUtil.IEOF_SEQUENCE_STRING)) {
    196         	return -1;
    197         }
    198         for (int i = 0; i < hex.length(); i ++) {
    199             char c = hex.charAt(i);
    200             if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
    201                 hex = hex.substring(0, i);
    202                 break;
    203             }
    204         }
    205         try {
    206         	return Integer.parseInt(hex, 16);
    207         } catch(NumberFormatException nfe) {
    208         	throw new DecodingException(nfe);
    209         }
    210     }
    211
    212     /**
    213      * parses all available message headers.
    214      * @param buffer @see {@link ChannelBuffer} that contains the headers.
    215      * @param maxSize the maximum size of all headers concatenated.
    216      * @return a list of String arrays containing [0] key [1] value of each header.
    217      * @throws TooLongFrameException if the maximum size is reached.
    218      */
    219 	public static List<String[]> readHeaders(ChannelBuffer buffer, int maxSize) throws DecodingException {
    220 		List<String[]> headerList = new ArrayList<String[]>();
    221 		SizeDelimiter sizeDelimiter = new SizeDelimiter(maxSize);
    222 		String line = IcapDecoderUtil.readSingleHeaderLine(buffer,sizeDelimiter);
    223 		String name = null;
    224 		String value = null;
    225 		if(line.length() != 0) {
    226 			while(line.length() != 0) {
    227 				if(name != null && IcapDecoderUtil.isHeaderLineSimpleValue(line)) {
    228 					value = value + ' ' + line.trim();
    229 				} else {
    230 					if(name != null) {
    231 						headerList.add(new String[]{name,value});
    232 					}
    233 					String[] header = IcapDecoderUtil.splitHeader(line);
    234 					name = header[0];
    235 					value = header[1];
    236 				}
    237 				line = IcapDecoderUtil.readSingleHeaderLine(buffer,sizeDelimiter);
    238 			}
    239             if (name != null) {
    240             	headerList.add(new String[]{name,value});
    241             }
    242 		}
    243 		return headerList;
    244 	}
    245
    246 	public static boolean isHeaderLineSimpleValue(String header) {
    247 		char firstChar = header.charAt(0);
    248 		return firstChar == ' ' || firstChar == '\t';
    249 	}
    250
    251 	/**
    252 	 * reads one individual header "key: value"
    253 	 * @param buffer which contains the request stream
    254 	 * @param sizeDelimiter the current header size, accumulated for all headers.
    255 	 * @return one complete header containing key and value.
    256 	 * @throws TooLongFrameException In case the total header length is exceeded.
    257 	 */
    258 	public static String readSingleHeaderLine(ChannelBuffer buffer, SizeDelimiter sizeDelimiter) throws DecodingException {
    259 		StringBuilder sb = new StringBuilder(64);
    260 		loop: for (;;) {
    261 			char nextByte = (char) buffer.readByte();
    262 			sizeDelimiter.increment();
    263
    264 			if(nextByte == IcapCodecUtil.CR) {
    265 				nextByte = (char) buffer.readByte();
    266 				sizeDelimiter.increment();
    267 				if (nextByte == IcapCodecUtil.LF) {
    268 					break loop;
    269 				}
    270 			} else if(nextByte == IcapCodecUtil.LF) {
    271 				break loop;
    272 			}
    273 			sb.append(nextByte);
    274 		}
    275 		return sb.toString();
    276 	}
    277
    278 	/**
    279 	 * Splits one header into key|value
    280 	 * @param sb
    281 	 * @return string array containing the key [0] and value [1] separated.
    282 	 */
    283     public static String[] splitHeader(String sb) {
    284         final int length = sb.length();
    285         int nameStart;
    286         int nameEnd;
    287         int colonEnd;
    288         int valueStart;
    289         int valueEnd;
    290
    291         nameStart = findNonWhitespace(sb, 0);
    292         for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
    293             char ch = sb.charAt(nameEnd);
    294             if (ch == ':' || Character.isWhitespace(ch)) {
    295                 break;
    296             }
    297         }
    298
    299         for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
    300             if (sb.charAt(colonEnd) == ':') {
    301                 colonEnd ++;
    302                 break;
    303             }
    304         }
    305
    306         valueStart = findNonWhitespace(sb, colonEnd);
    307         if (valueStart == length) {
    308             return new String[] {
    309                     sb.substring(nameStart, nameEnd),
    310                     ""
    311             };
    312         }
    313
    314         valueEnd = findEndOfString(sb);
    315         return new String[] {
    316                 sb.substring(nameStart, nameEnd),
    317                 sb.substring(valueStart, valueEnd)
    318         };
    319     }
    320 }