1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.common.extensions.compress;
20
21 import java.nio.ByteBuffer;
22 import java.util.zip.DataFormatException;
23 import java.util.zip.Deflater;
24 import java.util.zip.Inflater;
25
26 import org.eclipse.jetty.util.BufferUtil;
27 import org.eclipse.jetty.util.log.Log;
28 import org.eclipse.jetty.util.log.Logger;
29 import org.eclipse.jetty.websocket.api.BadPayloadException;
30 import org.eclipse.jetty.websocket.api.WriteCallback;
31 import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
32 import org.eclipse.jetty.websocket.api.extensions.Frame;
33 import org.eclipse.jetty.websocket.common.OpCode;
34 import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
35 import org.eclipse.jetty.websocket.common.frames.DataFrame;
36
37
38
39
40
41 public class DeflateFrameExtension extends AbstractExtension
42 {
43 private static final boolean BFINAL_HACK = Boolean.parseBoolean(System.getProperty("jetty.websocket.bfinal.hack","true"));
44 private static final Logger LOG = Log.getLogger(DeflateFrameExtension.class);
45
46 private static final int OVERHEAD = 64;
47
48 private static final byte[] TAIL = new byte[]
49 { 0x00, 0x00, (byte)0xFF, (byte)0xFF };
50 private int bufferSize = 64 * 1024;
51 private Deflater compressor;
52 private Inflater decompressor;
53
54 @Override
55 public String getName()
56 {
57 return "deflate-frame";
58 }
59
60 @Override
61 public synchronized void incomingFrame(Frame frame)
62 {
63 if (OpCode.isControlFrame(frame.getOpCode()) || !frame.isRsv1())
64 {
65
66 nextIncomingFrame(frame);
67 return;
68 }
69
70 if (!frame.hasPayload())
71 {
72
73 nextIncomingFrame(frame);
74 return;
75 }
76
77
78 ByteBuffer payload = frame.getPayload();
79 int inlen = payload.remaining();
80 byte compressed[] = new byte[inlen + TAIL.length];
81 payload.get(compressed,0,inlen);
82 System.arraycopy(TAIL,0,compressed,inlen,TAIL.length);
83 decompressor.setInput(compressed,0,compressed.length);
84
85
86 int maxSize = Math.max(getPolicy().getMaxTextMessageSize(),getPolicy().getMaxBinaryMessageBufferSize());
87 ByteAccumulator accumulator = new ByteAccumulator(maxSize);
88
89 DataFrame out = new DataFrame(frame);
90 out.setRsv1(false);
91
92
93 while (decompressor.getRemaining() > 0 && !decompressor.finished())
94 {
95 byte outbuf[] = new byte[Math.min(inlen * 2,bufferSize)];
96 try
97 {
98 int len = decompressor.inflate(outbuf);
99 if (len == 0)
100 {
101 if (decompressor.needsInput())
102 {
103 throw new BadPayloadException("Unable to inflate frame, not enough input on frame");
104 }
105 if (decompressor.needsDictionary())
106 {
107 throw new BadPayloadException("Unable to inflate frame, frame erroneously says it needs a dictionary");
108 }
109 }
110 if (len > 0)
111 {
112 accumulator.addBuffer(outbuf,0,len);
113 }
114 }
115 catch (DataFormatException e)
116 {
117 LOG.warn(e);
118 throw new BadPayloadException(e);
119 }
120 }
121
122
123 out.setPayload(accumulator.getByteBuffer(getBufferPool()));
124 nextIncomingFrame(out);
125 }
126
127
128
129
130
131
132 @Override
133 public boolean isRsv1User()
134 {
135 return true;
136 }
137
138 @Override
139 public synchronized void outgoingFrame(Frame frame, WriteCallback callback)
140 {
141 if (OpCode.isControlFrame(frame.getOpCode()))
142 {
143
144 nextOutgoingFrame(frame,callback);
145 return;
146 }
147
148 if (!frame.hasPayload())
149 {
150
151 nextOutgoingFrame(frame,callback);
152 return;
153 }
154
155 if (LOG.isDebugEnabled())
156 {
157 LOG.debug("outgoingFrame({}, {}) - {}",OpCode.name(frame.getOpCode()),callback != null?callback.getClass().getSimpleName():"<null>",
158 BufferUtil.toDetailString(frame.getPayload()));
159 }
160
161
162 byte uncompressed[] = BufferUtil.toArray(frame.getPayload());
163
164
165 if (!compressor.finished())
166 {
167 compressor.setInput(uncompressed,0,uncompressed.length);
168 byte compressed[] = new byte[uncompressed.length + OVERHEAD];
169
170 while (!compressor.needsInput())
171 {
172 int len = compressor.deflate(compressed,0,compressed.length,Deflater.SYNC_FLUSH);
173 ByteBuffer outbuf = getBufferPool().acquire(len,true);
174 BufferUtil.clearToFill(outbuf);
175
176 if (len > 0)
177 {
178 outbuf.put(compressed,0,len - 4);
179 }
180
181 BufferUtil.flipToFlush(outbuf,0);
182
183 if (len > 0 && BFINAL_HACK)
184 {
185
186
187
188
189
190
191
192 byte b0 = outbuf.get(0);
193 if ((b0 & 1) != 0)
194 {
195 outbuf.put(0,(b0 ^= 1));
196 }
197 }
198
199 DataFrame out = new DataFrame(frame);
200 out.setRsv1(true);
201 out.setPooledBuffer(true);
202 out.setPayload(outbuf);
203
204 if (!compressor.needsInput())
205 {
206
207 out.setFin(false);
208 nextOutgoingFrame(out,null);
209 }
210 else
211 {
212
213 nextOutgoingFrame(out,callback);
214 }
215 }
216 }
217 }
218
219 @Override
220 public void setConfig(ExtensionConfig config)
221 {
222 super.setConfig(config);
223
224 boolean nowrap = true;
225 compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
226 compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
227
228 decompressor = new Inflater(nowrap);
229 }
230
231 @Override
232 public String toString()
233 {
234 return this.getClass().getSimpleName() + "[]";
235 }
236 }