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.ChannelHandlerContext;
21 import org.jboss.netty.channel.Channels;
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 private int discardedBytes;
40
41 /**
42 * Creates a new decoder.
43 * @param maxLength the maximum length of the decoded frame.
44 * A {@link TooLongFrameException} is thrown if
45 * the length of the frame exceeds this value.
46 */
47 public LineBasedFrameDecoder(final int maxLength) {
48 this(maxLength, true, false);
49 }
50
51 /**
52 * Creates a new decoder.
53 * @param maxLength the maximum length of the decoded frame.
54 * A {@link TooLongFrameException} is thrown if
55 * the length of the frame exceeds this value.
56 * @param stripDelimiter whether the decoded frame should strip out the
57 * delimiter or not
58 * @param failFast If <tt>true</tt>, a {@link TooLongFrameException} is
59 * thrown as soon as the decoder notices the length of the
60 * frame will exceed <tt>maxFrameLength</tt> regardless of
61 * whether the entire frame has been read.
62 * If <tt>false</tt>, a {@link TooLongFrameException} is
63 * thrown after the entire frame that exceeds
64 * <tt>maxFrameLength</tt> has been read.
65 */
66 public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
67 this.maxLength = maxLength;
68 this.failFast = failFast;
69 this.stripDelimiter = stripDelimiter;
70 }
71
72 @Override
73 protected Object decode(final ChannelHandlerContext ctx,
74 final Channel channel,
75 final ChannelBuffer buffer) throws Exception {
76 final int eol = findEndOfLine(buffer);
77 if (!discarding) {
78 if (eol >= 0) {
79 final ChannelBuffer frame;
80 final int length = eol - buffer.readerIndex();
81 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
82
83 if (length > maxLength) {
84 buffer.readerIndex(eol + delimLength);
85 fail(ctx, length);
86 return null;
87 }
88
89 try {
90 if (stripDelimiter) {
91 frame = extractFrame(buffer, buffer.readerIndex(), length);
92 } else {
93 frame = extractFrame(buffer, buffer.readerIndex(), length + delimLength);
94 }
95 } finally {
96 buffer.skipBytes(length + delimLength);
97 }
98 return frame;
99 } else {
100 final int length = buffer.readableBytes();
101 if (length > maxLength) {
102 discardedBytes = length;
103 buffer.readerIndex(buffer.writerIndex());
104 discarding = true;
105 if (failFast) {
106 fail(ctx, "over " + discardedBytes);
107 }
108 }
109 return null;
110 }
111 } else {
112 if (eol >= 0) {
113 final int length = discardedBytes + eol - buffer.readerIndex();
114 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
115 buffer.readerIndex(eol + delimLength);
116 discardedBytes = 0;
117 discarding = false;
118 if (!failFast) {
119 fail(ctx, length);
120 }
121 } else {
122 discardedBytes = buffer.readableBytes();
123 buffer.readerIndex(buffer.writerIndex());
124 }
125 return null;
126 }
127 }
128
129 private void fail(final ChannelHandlerContext ctx, int length) {
130 fail(ctx, String.valueOf(length));
131 }
132
133 private void fail(final ChannelHandlerContext ctx, String length) {
134 Channels.fireExceptionCaught(
135 ctx.getChannel(),
136 new TooLongFrameException(
137 "frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));
138 }
139
140 /**
141 * Returns the index in the buffer of the end of line found.
142 * Returns -1 if no end of line was found in the buffer.
143 */
144 private static int findEndOfLine(final ChannelBuffer buffer) {
145 final int n = buffer.writerIndex();
146 for (int i = buffer.readerIndex(); i < n; i ++) {
147 final byte b = buffer.getByte(i);
148 if (b == '\n') {
149 return i;
150 } else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
151 return i; // \r\n
152 }
153 }
154 return -1; // Not found.
155 }
156 }