View Javadoc

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