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;
20
21 import java.nio.ByteBuffer;
22
23 import org.eclipse.jetty.util.BufferUtil;
24 import org.eclipse.jetty.util.StringUtil;
25 import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
26 import org.eclipse.jetty.util.Utf8StringBuilder;
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.ProtocolException;
31 import org.eclipse.jetty.websocket.api.StatusCode;
32 import org.eclipse.jetty.websocket.api.extensions.Frame;
33 import org.eclipse.jetty.websocket.common.frames.CloseFrame;
34
35 public class CloseInfo
36 {
37 private static final Logger LOG = Log.getLogger(CloseInfo.class);
38 private int statusCode;
39 private String reason;
40
41 public CloseInfo()
42 {
43 this(StatusCode.NO_CODE,null);
44 }
45
46 public CloseInfo(ByteBuffer payload, boolean validate)
47 {
48 this.statusCode = StatusCode.NO_CODE;
49 this.reason = null;
50
51 if ((payload == null) || (payload.remaining() == 0))
52 {
53 return;
54 }
55
56 ByteBuffer data = payload.slice();
57 if ((data.remaining() == 1) && (validate))
58 {
59 throw new ProtocolException("Invalid 1 byte payload");
60 }
61
62 if (data.remaining() >= 2)
63 {
64
65 statusCode = 0;
66 statusCode |= (data.get() & 0xFF) << 8;
67 statusCode |= (data.get() & 0xFF);
68
69 if (validate)
70 {
71 if ((statusCode < StatusCode.NORMAL) || (statusCode == StatusCode.UNDEFINED) || (statusCode == StatusCode.NO_CLOSE)
72 || (statusCode == StatusCode.NO_CODE) || ((statusCode > 1011) && (statusCode <= 2999)) || (statusCode >= 5000))
73 {
74 throw new ProtocolException("Invalid close code: " + statusCode);
75 }
76 }
77
78 if (data.remaining() > 0)
79 {
80
81 try
82 {
83 Utf8StringBuilder utf = new Utf8StringBuilder();
84 utf.append(data);
85 reason = utf.toString();
86 }
87 catch (NotUtf8Exception e)
88 {
89 if (validate)
90 {
91 throw new BadPayloadException("Invalid Close Reason",e);
92 }
93 else
94 {
95 LOG.warn(e);
96 }
97 }
98 catch (RuntimeException e)
99 {
100 if (validate)
101 {
102 throw new ProtocolException("Invalid Close Reason",e);
103 }
104 else
105 {
106 LOG.warn(e);
107 }
108 }
109 }
110 }
111 }
112
113 public CloseInfo(Frame frame)
114 {
115 this(frame.getPayload(),false);
116 }
117
118 public CloseInfo(Frame frame, boolean validate)
119 {
120 this(frame.getPayload(),validate);
121 }
122
123 public CloseInfo(int statusCode)
124 {
125 this(statusCode,null);
126 }
127
128 public CloseInfo(int statusCode, String reason)
129 {
130 this.statusCode = statusCode;
131 this.reason = reason;
132 }
133
134 private ByteBuffer asByteBuffer()
135 {
136 if ((statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || (statusCode == (-1)))
137 {
138
139 return null;
140 }
141
142 int len = 2;
143 byte utf[] = null;
144 if (StringUtil.isNotBlank(reason))
145 {
146 utf = StringUtil.getUtf8Bytes(reason);
147 len += utf.length;
148 }
149
150 ByteBuffer buf = BufferUtil.allocate(len);
151 BufferUtil.flipToFill(buf);
152 buf.put((byte)((statusCode >>> 8) & 0xFF));
153 buf.put((byte)((statusCode >>> 0) & 0xFF));
154
155 if (utf != null)
156 {
157 buf.put(utf,0,utf.length);
158 }
159 BufferUtil.flipToFlush(buf,0);
160
161 return buf;
162 }
163
164 public CloseFrame asFrame()
165 {
166 CloseFrame frame = new CloseFrame();
167 frame.setFin(true);
168 if ((statusCode >= 1000) && (statusCode != StatusCode.NO_CLOSE) && (statusCode != StatusCode.NO_CODE))
169 {
170 if (statusCode == StatusCode.FAILED_TLS_HANDSHAKE)
171 {
172 throw new ProtocolException("Close Frame with status code " + statusCode + " not allowed (per RFC6455)");
173 }
174 frame.setPayload(asByteBuffer());
175 }
176 return frame;
177 }
178
179 public String getReason()
180 {
181 return reason;
182 }
183
184 public int getStatusCode()
185 {
186 return statusCode;
187 }
188
189 public boolean isHarsh()
190 {
191 return !((statusCode == StatusCode.NORMAL) || (statusCode == StatusCode.NO_CODE));
192 }
193
194 public boolean isAbnormal()
195 {
196 return (statusCode != StatusCode.NORMAL);
197 }
198
199 @Override
200 public String toString()
201 {
202 return String.format("CloseInfo[code=%d,reason=%s]",statusCode,reason);
203 }
204 }