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 io.netty.handler.codec;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.util.ByteProcessor;
21
22 import java.util.List;
23
24 /**
25 * A decoder that splits the received [email protected] ByteBuf}s on line endings.
26 * <p>
27 * Both [email protected] "\n"} and [email protected] "\r\n"} are handled.
28 * For a more general delimiter-based decoder, see [email protected] DelimiterBasedFrameDecoder}.
29 */
30 public class LineBasedFrameDecoder extends ByteToMessageDecoder {
31
32 /** Maximum length of a frame we're willing to decode. */
33 private final int maxLength;
34 /** Whether or not to throw an exception as soon as we exceed maxLength. */
35 private final boolean failFast;
36 private final boolean stripDelimiter;
37
38 /** True if we're discarding input because we're already over maxLength. */
39 private boolean discarding;
40 private int discardedBytes;
41
42 /**
43 * Creates a new decoder.
44 * @param maxLength the maximum length of the decoded frame.
45 * A [email protected] TooLongFrameException} is thrown if
46 * the length of the frame exceeds this value.
47 */
48 public LineBasedFrameDecoder(final int maxLength) {
49 this(maxLength, true, false);
50 }
51
52 /**
53 * Creates a new decoder.
54 * @param maxLength the maximum length of the decoded frame.
55 * A [email protected] TooLongFrameException} is thrown if
56 * the length of the frame exceeds this value.
57 * @param stripDelimiter whether the decoded frame should strip out the
58 * delimiter or not
59 * @param failFast If <tt>true</tt>, a [email protected] TooLongFrameException} is
60 * thrown as soon as the decoder notices the length of the
61 * frame will exceed <tt>maxFrameLength</tt> regardless of
62 * whether the entire frame has been read.
63 * If <tt>false</tt>, a [email protected] TooLongFrameException} is
64 * thrown after the entire frame that exceeds
65 * <tt>maxFrameLength</tt> has been read.
66 */
67 public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
68 this.maxLength = maxLength;
69 this.failFast = failFast;
70 this.stripDelimiter = stripDelimiter;
71 }
72
73 @Override
74 protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
75 Object decoded = decode(ctx, in);
76 if (decoded != null) {
77 out.add(decoded);
78 }
79 }
80
81 /**
82 * Create a frame out of the [email protected] ByteBuf} and return it.
83 *
84 * @param ctx the [email protected] ChannelHandlerContext} which this [email protected] ByteToMessageDecoder} belongs to
85 * @param buffer the [email protected] ByteBuf} from which to read data
86 * @return frame the [email protected] ByteBuf} which represent the frame or [email protected] null} if no frame could
87 * be created.
88 */
89 protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
90 final int eol = findEndOfLine(buffer);
91 if (!discarding) {
92 if (eol >= 0) {
93 final ByteBuf frame;
94 final int length = eol - buffer.readerIndex();
95 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
96
97 if (length > maxLength) {
98 buffer.readerIndex(eol + delimLength);
99 fail(ctx, length);
100 return null;
101 }
102
103 if (stripDelimiter) {
104 frame = buffer.readSlice(length);
105 buffer.skipBytes(delimLength);
106 } else {
107 frame = buffer.readSlice(length + delimLength);
108 }
109
110 return frame.retain();
111 } else {
112 final int length = buffer.readableBytes();
113 if (length > maxLength) {
114 discardedBytes = length;
115 buffer.readerIndex(buffer.writerIndex());
116 discarding = true;
117 if (failFast) {
118 fail(ctx, "over " + discardedBytes);
119 }
120 }
121 return null;
122 }
123 } else {
124 if (eol >= 0) {
125 final int length = discardedBytes + eol - buffer.readerIndex();
126 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
127 buffer.readerIndex(eol + delimLength);
128 discardedBytes = 0;
129 discarding = false;
130 if (!failFast) {
131 fail(ctx, length);
132 }
133 } else {
134 discardedBytes += buffer.readableBytes();
135 buffer.readerIndex(buffer.writerIndex());
136 }
137 return null;
138 }
139 }
140
141 private void fail(final ChannelHandlerContext ctx, int length) {
142 fail(ctx, String.valueOf(length));
143 }
144
145 private void fail(final ChannelHandlerContext ctx, String length) {
146 ctx.fireExceptionCaught(
147 new TooLongFrameException(
148 "frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));
149 }
150
151 /**
152 * Returns the index in the buffer of the end of line found.
153 * Returns -1 if no end of line was found in the buffer.
154 */
155 private static int findEndOfLine(final ByteBuf buffer) {
156 int i = buffer.forEachByte(ByteProcessor.FIND_LF);
157 if (i > 0 && buffer.getByte(i - 1) == '\r') {
158 i--;
159 }
160 return i;
161 }
162 }