View Javadoc

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