1 /*
2 * Copyright 2015 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.netty.handler.codec.rtsp;
17
18 import java.util.regex.Pattern;
19
20 import io.netty.buffer.Unpooled;
21 import io.netty.handler.codec.http.DefaultFullHttpRequest;
22 import io.netty.handler.codec.http.DefaultFullHttpResponse;
23 import io.netty.handler.codec.http.DefaultHttpRequest;
24 import io.netty.handler.codec.http.DefaultHttpResponse;
25 import io.netty.handler.codec.http.HttpDecoderConfig;
26 import io.netty.handler.codec.http.HttpMessage;
27 import io.netty.handler.codec.http.HttpObjectDecoder;
28 import io.netty.handler.codec.http.HttpResponseStatus;
29
30 /**
31 * Decodes {@link io.netty.buffer.ByteBuf}s into RTSP messages represented in
32 * {@link HttpMessage}s.
33 * <p>
34 * <h3>Parameters that prevents excessive memory consumption</h3>
35 * <table border="1">
36 * <tr>
37 * <th>Name</th><th>Meaning</th>
38 * </tr>
39 * <tr>
40 * <td>{@code maxInitialLineLength}</td>
41 * <td>The maximum length of the initial line
42 * (e.g. {@code "SETUP / RTSP/1.0"} or {@code "RTSP/1.0 200 OK"})
43 * If the length of the initial line exceeds this value, a
44 * {@link io.netty.handler.codec.TooLongFrameException} will be raised.</td>
45 * </tr>
46 * <tr>
47 * <td>{@code maxHeaderSize}</td>
48 * <td>The maximum length of all headers. If the sum of the length of each
49 * header exceeds this value, a {@link io.netty.handler.codec.TooLongFrameException} will be
50 * raised.</td>
51 * </tr>
52 * <tr>
53 * <td>{@code maxContentLength}</td>
54 * <td>The maximum length of the content. If the content length exceeds this
55 * value, a {@link io.netty.handler.codec.TooLongFrameException} will be raised.</td>
56 * </tr>
57 * </table>
58 */
59 public class RtspDecoder extends HttpObjectDecoder {
60 /**
61 * Status code for unknown responses.
62 */
63 private static final HttpResponseStatus UNKNOWN_STATUS =
64 new HttpResponseStatus(999, "Unknown");
65 /**
66 * True if the message to decode is a request.
67 * False if the message to decode is a response.
68 */
69 private boolean isDecodingRequest;
70
71 /**
72 * Regex used on first line in message to detect if it is a response.
73 */
74 private static final Pattern versionPattern = Pattern.compile("RTSP/\\d\\.\\d");
75
76 /**
77 * Constant for default max content length.
78 */
79 public static final int DEFAULT_MAX_CONTENT_LENGTH = 8192;
80
81 /**
82 * Creates a new instance with the default
83 * {@code maxInitialLineLength (4096)}, {@code maxHeaderSize (8192)}, and
84 * {@code maxContentLength (8192)}.
85 */
86 public RtspDecoder() {
87 this(DEFAULT_MAX_INITIAL_LINE_LENGTH,
88 DEFAULT_MAX_HEADER_SIZE,
89 DEFAULT_MAX_CONTENT_LENGTH);
90 }
91
92 /**
93 * Creates a new instance with the specified parameters.
94 * @param maxInitialLineLength The max allowed length of initial line
95 * @param maxHeaderSize The max allowed size of header
96 * @param maxContentLength The max allowed content length
97 */
98 public RtspDecoder(final int maxInitialLineLength,
99 final int maxHeaderSize,
100 final int maxContentLength) {
101 super(new HttpDecoderConfig()
102 .setMaxInitialLineLength(maxInitialLineLength)
103 .setMaxHeaderSize(maxHeaderSize)
104 .setMaxChunkSize(maxContentLength * 2)
105 .setChunkedSupported(false));
106 }
107
108 /**
109 * Creates a new instance with the specified parameters.
110 * @param maxInitialLineLength The max allowed length of initial line
111 * @param maxHeaderSize The max allowed size of header
112 * @param maxContentLength The max allowed content length
113 * @param validateHeaders Set to true if headers should be validated
114 * @deprecated Use the {@link #RtspDecoder(HttpDecoderConfig)} constructor instead,
115 * or the {@link #RtspDecoder(int, int, int)} to always enable header validation.
116 */
117 @Deprecated
118 public RtspDecoder(final int maxInitialLineLength,
119 final int maxHeaderSize,
120 final int maxContentLength,
121 final boolean validateHeaders) {
122 super(new HttpDecoderConfig()
123 .setMaxInitialLineLength(maxInitialLineLength)
124 .setMaxHeaderSize(maxHeaderSize)
125 .setMaxChunkSize(maxContentLength * 2)
126 .setChunkedSupported(false)
127 .setValidateHeaders(validateHeaders));
128 }
129
130 /**
131 * Creates a new instance with the specified configuration.
132 */
133 public RtspDecoder(HttpDecoderConfig config) {
134 super(config.clone()
135 .setMaxChunkSize(2 * config.getMaxChunkSize())
136 .setChunkedSupported(false));
137 }
138
139 @Override
140 protected HttpMessage createMessage(final String[] initialLine)
141 throws Exception {
142 // If the first element of the initial line is a version string then
143 // this is a response
144 if (versionPattern.matcher(initialLine[0]).matches()) {
145 isDecodingRequest = false;
146 return new DefaultHttpResponse(RtspVersions.valueOf(initialLine[0]),
147 new HttpResponseStatus(Integer.parseInt(initialLine[1]),
148 initialLine[2]),
149 headersFactory);
150 } else {
151 isDecodingRequest = true;
152 return new DefaultHttpRequest(RtspVersions.valueOf(initialLine[2]),
153 RtspMethods.valueOf(initialLine[0]),
154 initialLine[1],
155 headersFactory);
156 }
157 }
158
159 @Override
160 protected boolean isContentAlwaysEmpty(final HttpMessage msg) {
161 // Unlike HTTP, RTSP always assumes zero-length body if Content-Length
162 // header is absent.
163 return super.isContentAlwaysEmpty(msg) || !msg.headers().contains(RtspHeaderNames.CONTENT_LENGTH);
164 }
165
166 @Override
167 protected HttpMessage createInvalidMessage() {
168 if (isDecodingRequest) {
169 return new DefaultFullHttpRequest(RtspVersions.RTSP_1_0,
170 RtspMethods.OPTIONS, "/bad-request", Unpooled.buffer(0), headersFactory, trailersFactory);
171 } else {
172 return new DefaultFullHttpResponse(
173 RtspVersions.RTSP_1_0, UNKNOWN_STATUS, Unpooled.buffer(0), headersFactory, trailersFactory);
174 }
175 }
176
177 @Override
178 protected boolean isDecodingRequest() {
179 return isDecodingRequest;
180 }
181 }