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