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.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                 request.append(cookie.toString());
172                 needDelim = true;
173             }
174             request.append("\r\n");
175         }
176 
177         // Other headers
178         for (String key : getHeaders().keySet())
179         {
180             if (FORBIDDEN_HEADERS.contains(key))
181             {
182                 LOG.debug("Skipping forbidden header - {}",key);
183                 continue; // skip
184             }
185             request.append(key).append(": ");
186             request.append(getHeader(key));
187             request.append("\r\n");
188         }
189 
190         // request header end
191         request.append("\r\n");
192         return request.toString();
193     }
194 
195     private final String genRandomKey()
196     {
197         byte[] bytes = new byte[16];
198         ThreadLocalRandom.current().nextBytes(bytes);
199         return new String(B64Code.encode(bytes));
200     }
201 
202     public String getKey()
203     {
204         return key;
205     }
206 
207     public void setCookiesFrom(CookieStore cookieStore)
208     {
209         if (cookieStore == null)
210         {
211             return;
212         }
213 
214         List<HttpCookie> existing = getCookies();
215         List<HttpCookie> extra = cookieStore.get(getRequestURI());
216 
217         List<HttpCookie> cookies = new ArrayList<>();
218         if (LazyList.hasEntry(existing))
219         {
220             cookies.addAll(existing);
221         }
222         if (LazyList.hasEntry(extra))
223         {
224             cookies.addAll(extra);
225         }
226         setCookies(cookies);
227     }
228 
229     @Override
230     public void setRequestURI(URI uri)
231     {
232         super.setRequestURI(uri);
233 
234         // parse parameter map
235         Map<String, List<String>> pmap = new HashMap<>();
236 
237         String query = uri.getQuery();
238 
239         if (StringUtil.isNotBlank(query))
240         {
241             MultiMap<String> params = new MultiMap<String>();
242             UrlEncoded.decodeTo(uri.getQuery(),params,StandardCharsets.UTF_8,MAX_KEYS);
243 
244             for (String key : params.keySet())
245             {
246                 List<String> values = params.getValues(key);
247                 if (values == null)
248                 {
249                     pmap.put(key,new ArrayList<String>());
250                 }
251                 else
252                 {
253                     // break link to original
254                     List<String> copy = new ArrayList<>();
255                     copy.addAll(values);
256                     pmap.put(key,copy);
257                 }
258             }
259 
260             super.setParameterMap(pmap);
261         }
262     }
263 }