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 * https://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.netty5.handler.codec;
17
18 import io.netty5.buffer.api.Buffer;
19 import io.netty5.channel.ChannelHandlerContext;
20 import io.netty5.util.ByteProcessor;
21
22 /**
23 * A decoder that splits the received {@link Buffer}s on line endings.
24 * <p>
25 * Both {@code "\n"} and {@code "\r\n"} are handled.
26 * <p>
27 * The byte stream is expected to be in UTF-8 character encoding or ASCII. The current implementation
28 * uses direct {@code byte} to {@code char} cast and then compares that {@code char} to a few low range
29 * ASCII characters like {@code '\n'} or {@code '\r'}. UTF-8 is not using low range [0..0x7F]
30 * byte values for multibyte codepoint representations therefore fully supported by this implementation.
31 * <p>
32 * For a more general delimiter-based decoder, see {@link DelimiterBasedFrameDecoder}.
33 */
34 public class LineBasedFrameDecoder extends ByteToMessageDecoder {
35
36 /** Maximum length of a frame we're willing to decode. */
37 private final int maxLength;
38 /** Whether or not to throw an exception as soon as we exceed maxLength. */
39 private final boolean failFast;
40 private final boolean stripDelimiter;
41
42 /** True if we're discarding input because we're already over maxLength. */
43 private boolean discarding;
44 private int discardedBytes;
45
46 /** Last scan position. */
47 private int offset;
48
49 /**
50 * Creates a new decoder.
51 * @param maxLength the maximum length of the decoded frame.
52 * A {@link TooLongFrameException} is thrown if
53 * the length of the frame exceeds this value.
54 */
55 public LineBasedFrameDecoder(final int maxLength) {
56 this(maxLength, true, false);
57 }
58
59 /**
60 * Creates a new decoder.
61 * @param maxLength the maximum length of the decoded frame.
62 * A {@link TooLongFrameException} is thrown if
63 * the length of the frame exceeds this value.
64 * @param stripDelimiter whether the decoded frame should strip out the
65 * delimiter or not
66 * @param failFast If <tt>true</tt>, a {@link TooLongFrameException} is
67 * thrown as soon as the decoder notices the length of the
68 * frame will exceed <tt>maxFrameLength</tt> regardless of
69 * whether the entire frame has been read.
70 * If <tt>false</tt>, a {@link TooLongFrameException} is
71 * thrown after the entire frame that exceeds
72 * <tt>maxFrameLength</tt> has been read.
73 */
74 public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
75 this.maxLength = maxLength;
76 this.failFast = failFast;
77 this.stripDelimiter = stripDelimiter;
78 }
79
80 @Override
81 protected final void decode(ChannelHandlerContext ctx, Buffer in) throws Exception {
82 Object decoded = decode0(ctx, in);
83 if (decoded != null) {
84 ctx.fireChannelRead(decoded);
85 }
86 }
87
88 /**
89 * Create a frame out of the {@link Buffer} and return it.
90 *
91 * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to
92 * @param buffer the {@link Buffer} from which to read data
93 * @return frame the {@link Buffer} which represent the frame or {@code null} if no frame could
94 * be created.
95 */
96 protected Object decode0(ChannelHandlerContext ctx, Buffer buffer) {
97 final int eol = findEndOfLine(buffer);
98 if (!discarding) {
99 if (eol >= 0) {
100 final Buffer frame;
101 final int length = eol - buffer.readerOffset();
102 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
103
104 if (length > maxLength) {
105 buffer.readerOffset(eol + delimLength);
106 fail(ctx, length);
107 return null;
108 }
109
110 if (stripDelimiter) {
111 frame = buffer.readSplit(length);
112 buffer.skipReadableBytes(delimLength);
113 } else {
114 frame = buffer.readSplit(length + delimLength);
115 }
116
117 return frame;
118 } else {
119 final int length = buffer.readableBytes();
120 if (length > maxLength) {
121 discardedBytes = length;
122 buffer.readerOffset(buffer.writerOffset());
123 discarding = true;
124 offset = 0;
125 if (failFast) {
126 fail(ctx, "over " + discardedBytes);
127 }
128 }
129 return null;
130 }
131 } else {
132 if (eol >= 0) {
133 final int length = discardedBytes + eol - buffer.readerOffset();
134 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
135 buffer.readerOffset(eol + delimLength);
136 discardedBytes = 0;
137 discarding = false;
138 if (!failFast) {
139 fail(ctx, length);
140 }
141 } else {
142 discardedBytes += buffer.readableBytes();
143 buffer.readerOffset(buffer.writerOffset());
144 // We skip everything in the buffer, we need to set the offset to 0 again.
145 offset = 0;
146 }
147 return null;
148 }
149 }
150
151 private void fail(final ChannelHandlerContext ctx, int length) {
152 fail(ctx, String.valueOf(length));
153 }
154
155 private void fail(final ChannelHandlerContext ctx, String length) {
156 ctx.fireChannelExceptionCaught(
157 new TooLongFrameException(
158 "frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));
159 }
160
161 /**
162 * Returns the index in the buffer of the end of line found.
163 * Returns -1 if no end of line was found in the buffer.
164 */
165 private int findEndOfLine(final Buffer buffer) {
166 int totalLength = buffer.readableBytes();
167 int index = buffer.readerOffset() + offset;
168 int i = buffer.openCursor(index, totalLength - offset).process(ByteProcessor.FIND_LF);
169 i = i == -1 ? -1 : index + i;
170 if (i >= 0) {
171 offset = 0;
172 if (i > 0 && buffer.getByte(i - 1) == '\r') {
173 i--;
174 }
175 } else {
176 offset = totalLength;
177 }
178 return i;
179 }
180 }