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