View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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;
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; // nothing to do
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              // Status Code
65              statusCode = 0; // start with 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                  // Reason
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             // codes that are not allowed to be used in endpoint.
139             return null;
140         }
141 
142         int len = 2; // status code
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 }