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.MultiMap;
35  import org.eclipse.jetty.util.StringUtil;
36  import org.eclipse.jetty.util.UrlEncoded;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.eclipse.jetty.websocket.api.UpgradeRequest;
40  import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
41  
42  /**
43   * Allowing a generate from a UpgradeRequest
44   */
45  public class ClientUpgradeRequest extends UpgradeRequest
46  {
47      private static final Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
48      private static final int MAX_KEYS = -1; // maximum number of parameter keys to decode
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                 request.append(cookie.toString());
171                 needDelim = true;
172             }
173             request.append("\r\n");
174         }
175 
176         // Other headers
177         for (String key : getHeaders().keySet())
178         {
179             if (FORBIDDEN_HEADERS.contains(key))
180             {
181                 LOG.debug("Skipping forbidden header - {}",key);
182                 continue; // skip
183             }
184             request.append(key).append(": ");
185             request.append(getHeader(key));
186             request.append("\r\n");
187         }
188 
189         // request header end
190         request.append("\r\n");
191         return request.toString();
192     }
193 
194     private final String genRandomKey()
195     {
196         byte[] bytes = new byte[16];
197         ThreadLocalRandom.current().nextBytes(bytes);
198         return new String(B64Code.encode(bytes));
199     }
200 
201     public String getKey()
202     {
203         return key;
204     }
205 
206     public void setCookiesFrom(CookieStore cookieStore)
207     {
208         if (cookieStore == null)
209         {
210             return;
211         }
212 
213         setCookies(cookieStore.get(getRequestURI()));
214     }
215 
216     @Override
217     public void setRequestURI(URI uri)
218     {
219         super.setRequestURI(uri);
220 
221         // parse parameter map
222         Map<String, List<String>> pmap = new HashMap<>();
223 
224         String query = uri.getQuery();
225 
226         if (StringUtil.isNotBlank(query))
227         {
228             MultiMap<String> params = new MultiMap<String>();
229             UrlEncoded.decodeTo(uri.getQuery(),params,StandardCharsets.UTF_8,MAX_KEYS);
230 
231             for (String key : params.keySet())
232             {
233                 List<String> values = params.getValues(key);
234                 if (values == null)
235                 {
236                     pmap.put(key,new ArrayList<String>());
237                 }
238                 else
239                 {
240                     // break link to original
241                     List<String> copy = new ArrayList<>();
242                     copy.addAll(values);
243                     pmap.put(key,copy);
244                 }
245             }
246 
247             super.setParameterMap(pmap);
248         }
249     }
250 }