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
23 import org.eclipse.jetty.util.log.Log;
24 import org.eclipse.jetty.util.log.Logger;
25 import org.eclipse.jetty.websocket.api.BatchMode;
26 import org.eclipse.jetty.websocket.api.WriteCallback;
27 import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
28 import org.eclipse.jetty.websocket.api.extensions.Frame;
29 import org.eclipse.jetty.websocket.common.OpCode;
30
31
32
33
34
35
36 public class PerMessageDeflateExtension extends CompressExtension
37 {
38 private static final Logger LOG = Log.getLogger(PerMessageDeflateExtension.class);
39
40 private ExtensionConfig configRequested;
41 private ExtensionConfig configNegotiated;
42 private boolean incomingContextTakeover = true;
43 private boolean outgoingContextTakeover = true;
44 private boolean incomingCompressed;
45
46 @Override
47 public String getName()
48 {
49 return "permessage-deflate";
50 }
51
52 @Override
53 public void incomingFrame(Frame frame)
54 {
55
56
57
58
59
60
61 if (frame.getType().isData())
62 incomingCompressed = frame.isRsv1();
63
64 if (OpCode.isControlFrame(frame.getOpCode()) || !frame.hasPayload() || !incomingCompressed)
65 {
66 nextIncomingFrame(frame);
67 return;
68 }
69
70 boolean appendTail = frame.isFin();
71 ByteBuffer payload = frame.getPayload();
72 int remaining = payload.remaining();
73 byte[] input = new byte[remaining + (appendTail ? TAIL_BYTES.length : 0)];
74 payload.get(input, 0, remaining);
75 if (appendTail)
76 System.arraycopy(TAIL_BYTES, 0, input, remaining, TAIL_BYTES.length);
77
78 forwardIncoming(frame, decompress(input));
79
80 if (frame.isFin())
81 incomingCompressed = false;
82 }
83
84 @Override
85 protected void nextIncomingFrame(Frame frame)
86 {
87 if (frame.isFin() && !incomingContextTakeover)
88 {
89 LOG.debug("Incoming Context Reset");
90 getInflater().reset();
91 }
92 super.nextIncomingFrame(frame);
93 }
94
95 @Override
96 protected void nextOutgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
97 {
98 if (frame.isFin() && !outgoingContextTakeover)
99 {
100 LOG.debug("Outgoing Context Reset");
101 getDeflater().reset();
102 }
103 super.nextOutgoingFrame(frame, callback, batchMode);
104 }
105
106 @Override
107 int getRsvUseMode()
108 {
109 return RSV_USE_ONLY_FIRST;
110 }
111
112 @Override
113 int getTailDropMode()
114 {
115 return TAIL_DROP_FIN_ONLY;
116 }
117
118 @Override
119 public void setConfig(final ExtensionConfig config)
120 {
121 configRequested = new ExtensionConfig(config);
122 configNegotiated = new ExtensionConfig(config.getName());
123
124 for (String key : config.getParameterKeys())
125 {
126 key = key.trim();
127 switch (key)
128 {
129 case "client_max_window_bits":
130 case "server_max_window_bits":
131 {
132
133
134 break;
135 }
136 case "client_no_context_takeover":
137 {
138 configNegotiated.setParameter("client_no_context_takeover");
139 switch (getPolicy().getBehavior())
140 {
141 case CLIENT:
142 incomingContextTakeover = false;
143 break;
144 case SERVER:
145 outgoingContextTakeover = false;
146 break;
147 }
148 break;
149 }
150 case "server_no_context_takeover":
151 {
152 configNegotiated.setParameter("server_no_context_takeover");
153 switch (getPolicy().getBehavior())
154 {
155 case CLIENT:
156 outgoingContextTakeover = false;
157 break;
158 case SERVER:
159 incomingContextTakeover = false;
160 break;
161 }
162 break;
163 }
164 default:
165 {
166 throw new IllegalArgumentException();
167 }
168 }
169 }
170
171 super.setConfig(configNegotiated);
172 }
173
174 @Override
175 public String toString()
176 {
177 return String.format("%s[requested=%s,negotiated=%s]",
178 getClass().getSimpleName(),
179 configRequested.getParameterizedName(),
180 configNegotiated.getParameterizedName());
181 }
182 }