View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.client;
20  
21  import java.net.CookieStore;
22  import java.net.HttpCookie;
23  import java.net.URI;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Random;
29  import java.util.Set;
30  
31  import org.eclipse.jetty.util.B64Code;
32  import org.eclipse.jetty.util.MultiMap;
33  import org.eclipse.jetty.util.StringUtil;
34  import org.eclipse.jetty.util.UrlEncoded;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  import org.eclipse.jetty.websocket.api.UpgradeRequest;
38  import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
39  
40  /**
41   * Allowing a generate from a UpgradeRequest
42   */
43  public class ClientUpgradeRequest extends UpgradeRequest
44  {
45      private final static Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
46      private final static int MAX_KEYS = -1; // maximum number of parameter keys to decode
47      private static final Set<String> FORBIDDEN_HEADERS;
48  
49      static
50      {
51          // headers not allowed to be set in ClientUpgradeRequest.headers
52          FORBIDDEN_HEADERS = new HashSet<>();
53          FORBIDDEN_HEADERS.add("cookie");
54          FORBIDDEN_HEADERS.add("upgrade");
55          FORBIDDEN_HEADERS.add("host");
56          FORBIDDEN_HEADERS.add("connection");
57          FORBIDDEN_HEADERS.add("sec-websocket-key");
58          FORBIDDEN_HEADERS.add("sec-websocket-extensions");
59          FORBIDDEN_HEADERS.add("sec-websocket-accept");
60          FORBIDDEN_HEADERS.add("sec-websocket-protocol");
61          FORBIDDEN_HEADERS.add("sec-websocket-version");
62          FORBIDDEN_HEADERS.add("pragma");
63          FORBIDDEN_HEADERS.add("cache-control");
64      }
65  
66      private final String key;
67  
68      public ClientUpgradeRequest()
69      {
70          super();
71          this.key = genRandomKey();
72      }
73  
74      protected ClientUpgradeRequest(URI requestURI)
75      {
76          super(requestURI);
77          this.key = genRandomKey();
78      }
79  
80      public String generate()
81      {
82          URI uri = getRequestURI();
83  
84          StringBuilder request = new StringBuilder(512);
85          request.append("GET ");
86          if (StringUtil.isBlank(uri.getPath()))
87          {
88              request.append("/");
89          }
90          else
91          {
92              request.append(uri.getPath());
93          }
94          if (StringUtil.isNotBlank(uri.getRawQuery()))
95          {
96              request.append("?").append(uri.getRawQuery());
97          }
98          request.append(" HTTP/1.1\r\n");
99  
100         request.append("Host: ").append(uri.getHost());
101         if (uri.getPort() > 0)
102         {
103             request.append(':').append(uri.getPort());
104         }
105         request.append("\r\n");
106 
107         // WebSocket specifics
108         request.append("Upgrade: websocket\r\n");
109         request.append("Connection: Upgrade\r\n");
110         request.append("Sec-WebSocket-Key: ").append(key).append("\r\n");
111         request.append("Sec-WebSocket-Version: 13\r\n"); // RFC-6455 specified version
112 
113         // (Per the hybi list): Add no-cache headers to avoid compatibility issue.
114         // There are some proxies that rewrite "Connection: upgrade"
115         // to "Connection: close" in the response if a request doesn't contain
116         // these headers.
117         request.append("Pragma: no-cache\r\n");
118         request.append("Cache-Control: no-cache\r\n");
119 
120         // Extensions
121         if (!getExtensions().isEmpty())
122         {
123             request.append("Sec-WebSocket-Extensions: ");
124             boolean needDelim = false;
125             for (ExtensionConfig ext : getExtensions())
126             {
127                 if (needDelim)
128                 {
129                     request.append(", ");
130                 }
131                 request.append(ext.getParameterizedName());
132                 needDelim = true;
133             }
134             request.append("\r\n");
135         }
136 
137         // Sub Protocols
138         if (!getSubProtocols().isEmpty())
139         {
140             request.append("Sec-WebSocket-Protocol: ");
141             boolean needDelim = false;
142             for (String protocol : getSubProtocols())
143             {
144                 if (needDelim)
145                 {
146                     request.append(", ");
147                 }
148                 request.append(protocol);
149                 needDelim = true;
150             }
151             request.append("\r\n");
152         }
153 
154         // Cookies
155         List<HttpCookie> cookies = getCookies();
156         if ((cookies != null) && (cookies.size() > 0))
157         {
158             request.append("Cookie: ");
159             boolean needDelim = false;
160             for (HttpCookie cookie : cookies)
161             {
162                 if (needDelim)
163                 {
164                     request.append("; ");
165                 }
166                 request.append(cookie.toString());
167                 needDelim = true;
168             }
169             request.append("\r\n");
170         }
171 
172         // Other headers
173         for (String key : getHeaders().keySet())
174         {
175             if (FORBIDDEN_HEADERS.contains(key.toLowerCase()))
176             {
177                 LOG.warn("Skipping forbidden header - {}",key);
178                 continue; // skip
179             }
180             request.append(key).append(": ");
181             request.append(getHeader(key));
182             request.append("\r\n");
183         }
184 
185         // request header end
186         request.append("\r\n");
187         return request.toString();
188     }
189 
190     private final String genRandomKey()
191     {
192         byte[] bytes = new byte[16];
193         new Random().nextBytes(bytes);
194         return new String(B64Code.encode(bytes));
195     }
196 
197     public String getKey()
198     {
199         return key;
200     }
201 
202     public void setCookiesFrom(CookieStore cookieStore)
203     {
204         if (cookieStore == null)
205         {
206             return;
207         }
208 
209         setCookies(cookieStore.get(getRequestURI()));
210     }
211 
212     @Override
213     public void setRequestURI(URI uri)
214     {
215         super.setRequestURI(uri);
216 
217         // parse parameter map
218         Map<String, String[]> pmap = new HashMap<>();
219 
220         String query = uri.getQuery();
221 
222         if (StringUtil.isNotBlank(query))
223         {
224             MultiMap<String> params = new MultiMap<String>();
225             UrlEncoded.decodeTo(uri.getQuery(),params,"UTF-8",MAX_KEYS);
226 
227             for (String key : params.keySet())
228             {
229                 List<String> values = params.getValues(key);
230                 if (values == null)
231                 {
232                     pmap.put(key,new String[0]);
233                 }
234                 else
235                 {
236                     int len = values.size();
237                     pmap.put(key,values.toArray(new String[len]));
238                 }
239             }
240 
241             super.setParameterMap(pmap);
242         }
243     }
244 }