1 //
2 // ========================================================================
3 // Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.websocket.common.extensions.compress;
20
21
22 import java.io.IOException;
23 import java.nio.ByteBuffer;
24 import java.util.concurrent.Future;
25
26 import javax.net.websocket.SendResult;
27
28 import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
29 import org.eclipse.jetty.websocket.api.extensions.Frame;
30 import org.eclipse.jetty.websocket.common.WebSocketFrame;
31 import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
32
33 /**
34 * Implementation of the <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-05.txt">x-webkit-deflate-frame</a> extension seen out
35 * in the wild.
36 */
37 public class FrameCompressionExtension extends AbstractExtension
38 {
39 private DeflateCompressionMethod method;
40
41 @Override
42 public void incomingFrame(Frame frame)
43 {
44 if (frame.getType().isControl() || !frame.isRsv1())
45 {
46 // Cannot modify incoming control frames or ones with RSV1 set.
47 nextIncomingFrame(frame);
48 return;
49 }
50
51 ByteBuffer data = frame.getPayload();
52 try
53 {
54 method.decompress().input(data);
55 while (!method.decompress().isDone())
56 {
57 ByteBuffer uncompressed = method.decompress().process();
58 WebSocketFrame out = new WebSocketFrame(frame).setPayload(uncompressed);
59 if (!method.decompress().isDone())
60 {
61 out.setFin(false);
62 }
63 out.setRsv1(false); // Unset RSV1 on decompressed frame
64 nextIncomingFrame(out);
65 }
66
67 // reset on every frame.
68 // method.decompress().end();
69 }
70 finally
71 {
72 // release original buffer (no longer needed)
73 getBufferPool().release(data);
74 }
75 }
76
77 /**
78 * Indicates use of RSV1 flag for indicating deflation is in use.
79 * <p>
80 * Also known as the "COMP" framing header bit
81 */
82 @Override
83 public boolean isRsv1User()
84 {
85 return true;
86 }
87
88 /**
89 * Indicate that this extensions is now responsible for TEXT Data Frame compliance to the WebSocket spec.
90 */
91 @Override
92 public boolean isTextDataDecoder()
93 {
94 return true;
95 }
96
97 @Override
98 public Future<SendResult> outgoingFrame(Frame frame) throws IOException
99 {
100 if (frame.getType().isControl())
101 {
102 // skip, cannot compress control frames.
103 return nextOutgoingFrame(frame);
104 }
105
106 Future<SendResult> future = null;
107
108 ByteBuffer data = frame.getPayload();
109 try
110 {
111 // deflate data
112 method.compress().input(data);
113 while (!method.compress().isDone())
114 {
115 ByteBuffer buf = method.compress().process();
116 WebSocketFrame out = new WebSocketFrame(frame).setPayload(buf);
117 out.setRsv1(true);
118 if (!method.compress().isDone())
119 {
120 out.setFin(false);
121 }
122
123 future = nextOutgoingFrame(out);
124 }
125
126 // reset on every frame.
127 method.compress().end();
128 }
129 finally
130 {
131 // free original data buffer
132 getBufferPool().release(data);
133 }
134
135 return future;
136 }
137
138 @Override
139 public void setConfig(ExtensionConfig config)
140 {
141 super.setConfig(config);
142
143 method = new DeflateCompressionMethod();
144 }
145
146 @Override
147 public String toString()
148 {
149 return this.getClass().getSimpleName() + "[]";
150 }
151 }