View Javadoc
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 }