1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package org.jboss.netty.handler.codec.frame;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.channel.Channel;
20 import org.jboss.netty.channel.Channels;
21 import org.jboss.netty.channel.ChannelHandlerContext;
22
23 /**
24 * A decoder that splits the received {@link ChannelBuffer}s on line endings.
25 * <p>
26 * Both {@code "\n"} and {@code "\r\n"} are handled.
27 * For a more general delimiter-based decoder, see {@link DelimiterBasedFrameDecoder}.
28 */
29 public class LineBasedFrameDecoder extends FrameDecoder {
30
31 /** Maximum length of a frame we're willing to decode. */
32 private final int maxLength;
33 /** Whether or not to throw an exception as soon as we exceed maxLength. */
34 private final boolean failFast;
35 private final boolean stripDelimiter;
36
37 /** True if we're discarding input because we're already over maxLength. */
38 private boolean discarding;
39
40 /**
41 * Creates a new decoder.
42 * @param maxLength the maximum length of the decoded frame.
43 * A {@link TooLongFrameException} is thrown if
44 * the length of the frame exceeds this value.
45 */
46 public LineBasedFrameDecoder(final int maxLength) {
47 this(maxLength, true, false);
48 }
49
50 /**
51 * Creates a new decoder.
52 * @param maxLength the maximum length of the decoded frame.
53 * A {@link TooLongFrameException} is thrown if
54 * the length of the frame exceeds this value.
55 * @param stripDelimiter whether the decoded frame should strip out the
56 * delimiter or not
57 * @param failFast If <tt>true</tt>, a {@link TooLongFrameException} is
58 * thrown as soon as the decoder notices the length of the
59 * frame will exceed <tt>maxFrameLength</tt> regardless of
60 * whether the entire frame has been read.
61 * If <tt>false</tt>, a {@link TooLongFrameException} is
62 * thrown after the entire frame that exceeds
63 * <tt>maxFrameLength</tt> has been read.
64 */
65 public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
66 this.maxLength = maxLength;
67 this.failFast = failFast;
68 this.stripDelimiter = stripDelimiter;
69 }
70
71 @Override
72 protected Object decode(final ChannelHandlerContext ctx,
73 final Channel channel,
74 final ChannelBuffer buffer) throws Exception {
75 final int eol = findEndOfLine(buffer);
76 if (eol != -1) {
77 final ChannelBuffer frame;
78 final int length = eol - buffer.readerIndex();
79 assert length >= 0: "Invalid length=" + length;
80 if (discarding) {
81 frame = null;
82 buffer.skipBytes(length);
83 if (!failFast) {
84 fail(ctx, "over " + (maxLength + length) + " bytes");
85 }
86 } else {
87 int delimLength;
88 final byte delim = buffer.getByte(buffer.readerIndex() + length);
89 if (delim == '\r') {
90 delimLength = 2; // Skip the \r\n.
91 } else {
92 delimLength = 1;
93 }
94 if (stripDelimiter) {
95 frame = extractFrame(buffer, buffer.readerIndex(), length);
96 } else {
97 frame = extractFrame(buffer, buffer.readerIndex(), length + delimLength);
98 }
99 buffer.skipBytes(length + delimLength);
100 }
101 return frame;
102 }
103
104 final int buffered = buffer.readableBytes();
105 if (!discarding && buffered > maxLength) {
106 discarding = true;
107 if (failFast) {
108 fail(ctx, buffered + " bytes buffered already");
109 }
110 }
111 if (discarding) {
112 buffer.skipBytes(buffer.readableBytes());
113 }
114 return null;
115 }
116
117 private void fail(final ChannelHandlerContext ctx, final String msg) {
118 Channels.fireExceptionCaught(ctx.getChannel(),
119 new TooLongFrameException("Frame length exceeds " + maxLength + " ("
120 + msg + ')'));
121 }
122
123 /**
124 * Returns the index in the buffer of the end of line found.
125 * Returns -1 if no end of line was found in the buffer.
126 */
127 private static int findEndOfLine(final ChannelBuffer buffer) {
128 final int n = buffer.writerIndex();
129 for (int i = buffer.readerIndex(); i < n; i ++) {
130 final byte b = buffer.getByte(i);
131 if (b == '\n') {
132 return i;
133 } else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
134 return i; // \r\n
135 }
136 }
137 return -1; // Not found.
138 }
139
140 }