View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.cache;
28  
29  import static org.mockito.Mockito.mock;
30  import static org.mockito.Mockito.verify;
31  import static org.mockito.Mockito.when;
32  
33  import org.apache.hc.client5.http.cache.HttpCacheEntry;
34  import org.apache.hc.client5.http.classic.methods.HttpGet;
35  import org.apache.hc.core5.http.Header;
36  import org.apache.hc.core5.http.HttpHost;
37  import org.apache.hc.core5.http.HttpRequest;
38  import org.apache.hc.core5.http.message.BasicHeader;
39  import org.apache.hc.core5.http.message.BasicHeaderIterator;
40  import org.apache.hc.core5.http.message.BasicHttpRequest;
41  import org.junit.Assert;
42  import org.junit.Before;
43  import org.junit.Test;
44  
45  @SuppressWarnings({"boxing","static-access"}) // this is test code
46  public class TestCacheKeyGenerator {
47  
48      private static final BasicHttpRequest REQUEST_FULL_EPISODES = new BasicHttpRequest("GET",
49              "/full_episodes");
50      private static final BasicHttpRequest REQUEST_ROOT = new BasicHttpRequest("GET", "/");
51  
52      private CacheKeyGenerator extractor;
53      private HttpHost defaultHost;
54      private HttpCacheEntry mockEntry;
55      private HttpRequest mockRequest;
56  
57      @Before
58      public void setUp() throws Exception {
59          defaultHost = new HttpHost("foo.example.com");
60          mockEntry = mock(HttpCacheEntry.class);
61          mockRequest = mock(HttpRequest.class);
62          extractor = CacheKeyGenerator.INSTANCE;
63      }
64  
65      @Test
66      public void testExtractsUriFromAbsoluteUriInRequest() {
67          final HttpHost host = new HttpHost("bar.example.com");
68          final HttpRequest req = new HttpGet("http://foo.example.com/");
69          Assert.assertEquals("http://foo.example.com:80/", extractor.generateKey(host, req));
70      }
71  
72      @Test
73      public void testGetURIWithDefaultPortAndScheme() {
74          Assert.assertEquals("http://www.comcast.net:80/", extractor.generateKey(new HttpHost(
75                  "www.comcast.net"), REQUEST_ROOT));
76  
77          Assert.assertEquals("http://www.fancast.com:80/full_episodes", extractor.generateKey(new HttpHost(
78                  "www.fancast.com"), REQUEST_FULL_EPISODES));
79      }
80  
81      @Test
82      public void testGetURIWithDifferentScheme() {
83          Assert.assertEquals("https://www.comcast.net:443/", extractor.generateKey(
84                  new HttpHost("https", "www.comcast.net", -1), REQUEST_ROOT));
85  
86          Assert.assertEquals("myhttp://www.fancast.com/full_episodes", extractor.generateKey(
87                  new HttpHost("myhttp", "www.fancast.com", -1), REQUEST_FULL_EPISODES));
88      }
89  
90      @Test
91      public void testGetURIWithDifferentPort() {
92          Assert.assertEquals("http://www.comcast.net:8080/", extractor.generateKey(new HttpHost(
93                  "www.comcast.net", 8080), REQUEST_ROOT));
94  
95          Assert.assertEquals("http://www.fancast.com:9999/full_episodes", extractor.generateKey(
96                  new HttpHost("www.fancast.com", 9999), REQUEST_FULL_EPISODES));
97      }
98  
99      @Test
100     public void testGetURIWithDifferentPortAndScheme() {
101         Assert.assertEquals("https://www.comcast.net:8080/", extractor.generateKey(
102                 new HttpHost("https", "www.comcast.net", 8080), REQUEST_ROOT));
103 
104         Assert.assertEquals("myhttp://www.fancast.com:9999/full_episodes", extractor.generateKey(
105                 new HttpHost("myhttp", "www.fancast.com", 9999), REQUEST_FULL_EPISODES));
106     }
107 
108     @Test
109     public void testGetURIWithQueryParameters() {
110         Assert.assertEquals("http://www.comcast.net:80/?foo=bar", extractor.generateKey(
111                 new HttpHost("http", "www.comcast.net", -1), new BasicHttpRequest("GET", "/?foo=bar")));
112         Assert.assertEquals("http://www.fancast.com:80/full_episodes?foo=bar", extractor.generateKey(
113                 new HttpHost("http", "www.fancast.com", -1), new BasicHttpRequest("GET",
114                         "/full_episodes?foo=bar")));
115     }
116 
117     @Test
118     public void testGetVariantURIWithNoVaryHeaderReturnsNormalURI() {
119         final String theURI = "theURI";
120         when(mockEntry.hasVariants()).thenReturn(false);
121         extractor = new CacheKeyGenerator() {
122             @Override
123             public String generateKey(final HttpHost h, final HttpRequest request) {
124                 Assert.assertSame(defaultHost, h);
125                 Assert.assertSame(mockRequest, request);
126                 return theURI;
127             }
128         };
129 
130         final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
131         verify(mockEntry).hasVariants();
132         Assert.assertSame(theURI, result);
133     }
134 
135     @Test
136     public void testGetVariantURIWithSingleValueVaryHeaderPrepends() {
137         final String theURI = "theURI";
138         final Header[] varyHeaders = { new BasicHeader("Vary", "Accept-Encoding") };
139         final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
140 
141         extractor = new CacheKeyGenerator() {
142             @Override
143             public String generateKey(final HttpHost h, final HttpRequest request) {
144                 Assert.assertSame(defaultHost, h);
145                 Assert.assertSame(mockRequest, request);
146                 return theURI;
147             }
148         };
149         when(mockEntry.hasVariants()).thenReturn(true);
150         when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
151         when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
152 
153         final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
154 
155         verify(mockEntry).hasVariants();
156         verify(mockEntry).headerIterator("Vary");
157         verify(mockRequest).getHeaders("Accept-Encoding");
158         Assert.assertEquals("{Accept-Encoding=gzip}" + theURI, result);
159     }
160 
161     @Test
162     public void testGetVariantURIWithMissingRequestHeader() {
163         final String theURI = "theURI";
164         final Header[] noHeaders = new Header[0];
165         final Header[] varyHeaders = { new BasicHeader("Vary", "Accept-Encoding") };
166         extractor = new CacheKeyGenerator() {
167             @Override
168             public String generateKey(final HttpHost h, final HttpRequest request) {
169                 Assert.assertSame(defaultHost, h);
170                 Assert.assertSame(mockRequest, request);
171                 return theURI;
172             }
173         };
174         when(mockEntry.hasVariants()).thenReturn(true);
175         when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
176         when(mockRequest.getHeaders("Accept-Encoding"))
177                 .thenReturn(noHeaders);
178 
179         final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
180 
181         verify(mockEntry).hasVariants();
182         verify(mockEntry).headerIterator("Vary");
183         verify(mockRequest).getHeaders("Accept-Encoding");
184         Assert.assertEquals("{Accept-Encoding=}" + theURI, result);
185     }
186 
187     @Test
188     public void testGetVariantURIAlphabetizesWithMultipleVaryingHeaders() {
189         final String theURI = "theURI";
190         final Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent, Accept-Encoding") };
191         final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
192         final Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
193         extractor = new CacheKeyGenerator() {
194             @Override
195             public String generateKey(final HttpHost h, final HttpRequest request) {
196                 Assert.assertSame(defaultHost, h);
197                 Assert.assertSame(mockRequest, request);
198                 return theURI;
199             }
200         };
201         when(mockEntry.hasVariants()).thenReturn(true);
202         when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
203         when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
204         when(mockRequest.getHeaders("User-Agent")).thenReturn(uaHeaders);
205 
206         final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
207 
208         verify(mockEntry).hasVariants();
209         verify(mockEntry).headerIterator("Vary");
210         verify(mockRequest).getHeaders("Accept-Encoding");
211         verify(mockRequest).getHeaders("User-Agent");
212         Assert.assertEquals("{Accept-Encoding=gzip&User-Agent=browser}" + theURI, result);
213     }
214 
215     @Test
216     public void testGetVariantURIHandlesMultipleVaryHeaders() {
217         final String theURI = "theURI";
218         final Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent"),
219                 new BasicHeader("Vary", "Accept-Encoding") };
220         final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
221         final Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
222         extractor = new CacheKeyGenerator() {
223             @Override
224             public String generateKey(final HttpHost h, final HttpRequest request) {
225                 Assert.assertSame(defaultHost, h);
226                 Assert.assertSame(mockRequest, request);
227                 return theURI;
228             }
229         };
230         when(mockEntry.hasVariants()).thenReturn(true);
231         when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
232         when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
233         when(mockRequest.getHeaders("User-Agent")).thenReturn(uaHeaders);
234 
235         final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
236 
237         verify(mockEntry).hasVariants();
238         verify(mockEntry).headerIterator("Vary");
239         verify(mockRequest).getHeaders("Accept-Encoding");
240         verify(mockRequest).getHeaders("User-Agent");
241         Assert.assertEquals("{Accept-Encoding=gzip&User-Agent=browser}" + theURI, result);
242     }
243 
244     @Test
245     public void testGetVariantURIHandlesMultipleLineRequestHeaders() {
246         final String theURI = "theURI";
247         final Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent, Accept-Encoding") };
248         final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip"),
249                 new BasicHeader("Accept-Encoding", "deflate") };
250         final Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
251         extractor = new CacheKeyGenerator() {
252             @Override
253             public String generateKey(final HttpHost h, final HttpRequest request) {
254                 Assert.assertSame(defaultHost, h);
255                 Assert.assertSame(mockRequest, request);
256                 return theURI;
257             }
258         };
259         when(mockEntry.hasVariants()).thenReturn(true);
260         when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
261         when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
262         when(mockRequest.getHeaders("User-Agent")).thenReturn(uaHeaders);
263 
264         final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
265 
266         verify(mockEntry).hasVariants();
267         verify(mockEntry).headerIterator("Vary");
268         verify(mockRequest).getHeaders("Accept-Encoding");
269         verify(mockRequest).getHeaders("User-Agent");
270         Assert.assertEquals("{Accept-Encoding=gzip%2C+deflate&User-Agent=browser}" + theURI, result);
271     }
272 
273     /*
274      * "When comparing two URIs to decide if they match or not, a client
275      * SHOULD use a case-sensitive octet-by-octet comparison of the entire
276      * URIs, with these exceptions:
277      * - A port that is empty or not given is equivalent to the default
278      * port for that URI-reference;
279      * - Comparisons of host names MUST be case-insensitive;
280      * - Comparisons of scheme names MUST be case-insensitive;
281      * - An empty abs_path is equivalent to an abs_path of "/".
282      * Characters other than those in the 'reserved' and 'unsafe' sets
283      * (see RFC 2396 [42]) are equivalent to their '"%" HEX HEX' encoding."
284      *
285      * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.3
286      */
287     @Test
288     public void testEmptyPortEquivalentToDefaultPortForHttp() {
289         final HttpHost host1 = new HttpHost("foo.example.com:");
290         final HttpHost host2 = new HttpHost("foo.example.com:80");
291         final HttpRequest req = new BasicHttpRequest("GET", "/");
292         Assert.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
293     }
294 
295     @Test
296     public void testEmptyPortEquivalentToDefaultPortForHttps() {
297         final HttpHost host1 = new HttpHost("https", "foo.example.com", -1);
298         final HttpHost host2 = new HttpHost("https", "foo.example.com", 443);
299         final HttpRequest req = new BasicHttpRequest("GET", "/");
300         final String uri1 = extractor.generateKey(host1, req);
301         final String uri2 = extractor.generateKey(host2, req);
302         Assert.assertEquals(uri1, uri2);
303     }
304 
305     @Test
306     public void testEmptyPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
307         final HttpHost host = new HttpHost("https", "foo.example.com", -1);
308         final HttpGet get1 = new HttpGet("https://bar.example.com:/");
309         final HttpGet get2 = new HttpGet("https://bar.example.com:443/");
310         final String uri1 = extractor.generateKey(host, get1);
311         final String uri2 = extractor.generateKey(host, get2);
312         Assert.assertEquals(uri1, uri2);
313     }
314 
315     @Test
316     public void testNotProvidedPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
317         final HttpHost host = new HttpHost("https", "foo.example.com", -1);
318         final HttpGet get1 = new HttpGet("https://bar.example.com/");
319         final HttpGet get2 = new HttpGet("https://bar.example.com:443/");
320         final String uri1 = extractor.generateKey(host, get1);
321         final String uri2 = extractor.generateKey(host, get2);
322         Assert.assertEquals(uri1, uri2);
323     }
324 
325     @Test
326     public void testNotProvidedPortEquivalentToDefaultPortForHttp() {
327         final HttpHost host1 = new HttpHost("foo.example.com");
328         final HttpHost host2 = new HttpHost("foo.example.com:80");
329         final HttpRequest req = new BasicHttpRequest("GET", "/");
330         Assert.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
331     }
332 
333     @Test
334     public void testHostNameComparisonsAreCaseInsensitive() {
335         final HttpHost host1 = new HttpHost("foo.example.com");
336         final HttpHost host2 = new HttpHost("FOO.EXAMPLE.COM");
337         final HttpRequest req = new BasicHttpRequest("GET", "/");
338         Assert.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
339     }
340 
341     @Test
342     public void testSchemeNameComparisonsAreCaseInsensitive() {
343         final HttpHost host1 = new HttpHost("http", "foo.example.com", -1);
344         final HttpHost host2 = new HttpHost("HTTP", "foo.example.com", -1);
345         final HttpRequest req = new BasicHttpRequest("GET", "/");
346         Assert.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
347     }
348 
349     @Test
350     public void testEmptyAbsPathIsEquivalentToSlash() {
351         final HttpHost host = new HttpHost("foo.example.com");
352         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
353         final HttpRequest req2 = new HttpGet("http://foo.example.com");
354         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
355     }
356 
357     @Test
358     public void testExtraDotSegmentsAreIgnored() {
359         final HttpHost host = new HttpHost("foo.example.com");
360         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
361         final HttpRequest req2 = new HttpGet("http://foo.example.com/./");
362         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
363     }
364 
365     @Test
366     public void testExtraDotDotSegmentsAreIgnored() {
367         final HttpHost host = new HttpHost("foo.example.com");
368         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
369         final HttpRequest req2 = new HttpGet("http://foo.example.com/.././../");
370         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
371     }
372 
373     @Test
374     public void testIntermidateDotDotSegementsAreEquivalent() {
375         final HttpHost host = new HttpHost("foo.example.com");
376         final HttpRequest req1 = new BasicHttpRequest("GET", "/home.html");
377         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/../home.html");
378         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
379     }
380 
381     @Test
382     public void testIntermidateEncodedDotDotSegementsAreEquivalent() {
383         final HttpHost host = new HttpHost("foo.example.com");
384         final HttpRequest req1 = new BasicHttpRequest("GET", "/home.html");
385         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/../home.html");
386         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
387     }
388 
389     @Test
390     public void testIntermidateDotSegementsAreEquivalent() {
391         final HttpHost host = new HttpHost("foo.example.com");
392         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
393         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/./home.html");
394         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
395     }
396 
397     @Test
398     public void testEquivalentPathEncodingsAreEquivalent() {
399         final HttpHost host = new HttpHost("foo.example.com");
400         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
401         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home.html");
402         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
403     }
404 
405     @Test
406     public void testEquivalentExtraPathEncodingsAreEquivalent() {
407         final HttpHost host = new HttpHost("foo.example.com");
408         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
409         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home.html");
410         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
411     }
412 
413     @Test
414     public void testEquivalentExtraPathEncodingsWithPercentAreEquivalent() {
415         final HttpHost host = new HttpHost("foo.example.com");
416         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home%20folder.html");
417         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home%20folder.html");
418         Assert.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
419     }
420 }