1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.codec.http.websocketx.extensions.compression;
17
18 import io.netty5.buffer.api.Buffer;
19 import io.netty5.buffer.api.CompositeBuffer;
20 import io.netty5.buffer.api.DefaultBufferAllocators;
21 import io.netty5.util.Send;
22 import io.netty5.channel.ChannelHandlerContext;
23 import io.netty5.channel.embedded.EmbeddedChannel;
24 import io.netty5.handler.codec.CodecException;
25 import io.netty5.handler.codec.compression.ZlibCodecFactory;
26 import io.netty5.handler.codec.compression.ZlibWrapper;
27 import io.netty5.handler.codec.http.websocketx.BinaryWebSocketFrame;
28 import io.netty5.handler.codec.http.websocketx.ContinuationWebSocketFrame;
29 import io.netty5.handler.codec.http.websocketx.TextWebSocketFrame;
30 import io.netty5.handler.codec.http.websocketx.WebSocketFrame;
31 import io.netty5.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
32 import io.netty5.handler.codec.http.websocketx.extensions.WebSocketExtensionFilter;
33 import io.netty5.util.internal.SilentDispose;
34
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Objects;
38 import java.util.function.Supplier;
39
40
41
42
43
44 abstract class DeflateDecoder extends WebSocketExtensionDecoder {
45
46 static final Supplier<Buffer> FRAME_TAIL;
47 static final int FRAME_TAIL_LENGTH;
48 static {
49 byte[] frameTail = { 0x00, 0x00, (byte) 0xff, (byte) 0xff };
50 FRAME_TAIL = DefaultBufferAllocators.preferredAllocator().constBufferSupplier(frameTail);
51 FRAME_TAIL_LENGTH = frameTail.length;
52 }
53
54 private final boolean noContext;
55 private final WebSocketExtensionFilter extensionDecoderFilter;
56
57 private EmbeddedChannel decoder;
58
59
60
61
62
63
64
65 DeflateDecoder(boolean noContext, WebSocketExtensionFilter extensionDecoderFilter) {
66 this.noContext = noContext;
67 this.extensionDecoderFilter = Objects.requireNonNull(extensionDecoderFilter, "extensionDecoderFilter");
68 }
69
70
71
72
73 protected WebSocketExtensionFilter extensionDecoderFilter() {
74 return extensionDecoderFilter;
75 }
76
77 protected abstract boolean appendFrameTail(WebSocketFrame msg);
78
79 protected abstract int newRsv(WebSocketFrame msg);
80
81 @Override
82 protected final void decode(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception {
83 throw new UnsupportedOperationException("DeflateDecoder uses decodeAndClose().");
84 }
85
86 @Override
87 protected void decodeAndClose(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception {
88 final Buffer decompressedContent = decompressContent(ctx, msg);
89
90 final WebSocketFrame outMsg;
91 if (msg instanceof TextWebSocketFrame) {
92 outMsg = new TextWebSocketFrame(msg.isFinalFragment(), newRsv(msg), decompressedContent);
93 } else if (msg instanceof BinaryWebSocketFrame) {
94 outMsg = new BinaryWebSocketFrame(msg.isFinalFragment(), newRsv(msg), decompressedContent);
95 } else if (msg instanceof ContinuationWebSocketFrame) {
96 outMsg = new ContinuationWebSocketFrame(msg.isFinalFragment(), newRsv(msg), decompressedContent);
97 } else {
98 SilentDispose.tryPropagatingDispose(msg);
99 throw new CodecException("unexpected frame type: " + msg.getClass().getName());
100 }
101
102 ctx.fireChannelRead(outMsg);
103 }
104
105 @Override
106 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
107 cleanup();
108 super.handlerRemoved(ctx);
109 }
110
111 @Override
112 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
113 cleanup();
114 super.channelInactive(ctx);
115 }
116
117 private Buffer decompressContent(ChannelHandlerContext ctx, WebSocketFrame msg) {
118 if (decoder == null) {
119 if (!(msg instanceof TextWebSocketFrame) && !(msg instanceof BinaryWebSocketFrame)) {
120 throw new CodecException("unexpected initial frame type: " + msg.getClass().getName());
121 }
122 decoder = new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
123 }
124
125 boolean readable = msg.binaryData().readableBytes() > 0;
126 boolean emptyDeflateBlock = isEmptyDeflateBlock(msg.binaryData());
127
128 decoder.writeInbound(msg.binaryData());
129 if (appendFrameTail(msg)) {
130 decoder.writeInbound(FRAME_TAIL.get());
131 }
132
133 List<Send<Buffer>> bufferList = new ArrayList<>();
134 for (;;) {
135 Buffer partUncompressedContent = decoder.readInbound();
136 if (partUncompressedContent == null) {
137 break;
138 }
139 if (partUncompressedContent.readableBytes() == 0) {
140 partUncompressedContent.close();
141 continue;
142 }
143 bufferList.add(partUncompressedContent.send());
144 }
145 CompositeBuffer compositeDecompressedContent = ctx.bufferAllocator().compose(bufferList);
146
147
148
149 if (!emptyDeflateBlock && readable && compositeDecompressedContent.countReadableComponents() <= 0) {
150
151
152 if (!(msg instanceof ContinuationWebSocketFrame)) {
153 compositeDecompressedContent.close();
154 throw new CodecException("cannot read uncompressed buffer");
155 }
156 }
157
158 if (msg.isFinalFragment() && noContext) {
159 cleanup();
160 }
161
162 return compositeDecompressedContent;
163 }
164
165 private static boolean isEmptyDeflateBlock(Buffer binaryData) {
166 return binaryData.readableBytes() == 1 && binaryData.getByte(binaryData.readerOffset()) == 0;
167 }
168
169 private void cleanup() {
170 if (decoder != null) {
171
172 decoder.finishAndReleaseAll();
173 decoder = null;
174 }
175 }
176 }