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 }