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