According to RFC 2616 [1] header field names are case insensitive. Header fields defined as a comma separated list can have multiple header fields with the same field name. Add a method to HttpConnection which retrieves all values with a given header field name with the field name compared case insensitive. [1] https://tools.ietf.org/html/rfc2616#section-4.2" Change-Id: I7f601b21cda99e84f43f866c7c7cb4cb0e3cf5c3 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v5.2.0.201811281532-m3
@@ -23,6 +23,7 @@ Import-Package: org.apache.http;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.client;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.conn;version="[4.3.0,5.0.0)", | |||
org.apache.http.params;version="[4.3.0,5.0.0)", | |||
org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)", | |||
org.eclipse.jgit.nls;version="[5.2.0,5.3.0)", | |||
org.eclipse.jgit.transport.http;version="[5.2.0,5.3.0)", | |||
org.eclipse.jgit.util;version="[5.2.0,5.3.0)" |
@@ -58,10 +58,13 @@ import java.net.URL; | |||
import java.security.KeyManagementException; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.security.SecureRandom; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
import javax.net.ssl.HostnameVerifier; | |||
import javax.net.ssl.KeyManager; | |||
@@ -90,6 +93,7 @@ import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||
import org.apache.http.impl.client.HttpClientBuilder; | |||
import org.apache.http.impl.client.HttpClients; | |||
import org.apache.http.impl.conn.BasicHttpClientConnectionManager; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.transport.http.HttpConnection; | |||
import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText; | |||
import org.eclipse.jgit.util.TemporaryBuffer; | |||
@@ -347,11 +351,17 @@ public class HttpClientConnection implements HttpConnection { | |||
// will return only the first field | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String getHeaderField(String name) { | |||
public String getHeaderField(@NonNull String name) { | |||
Header header = resp.getFirstHeader(name); | |||
return (header == null) ? null : header.getValue(); | |||
} | |||
@Override | |||
public List<String> getHeaderFields(@NonNull String name) { | |||
return Collections.unmodifiableList(Arrays.asList(resp.getHeaders(name)) | |||
.stream().map(Header::getValue).collect(Collectors.toList())); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public int getContentLength() { |
@@ -10,6 +10,7 @@ Bundle-ActivationPolicy: lazy | |||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | |||
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", | |||
com.jcraft.jsch;version="[0.1.54,0.2.0)", | |||
net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)", | |||
org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)", | |||
org.eclipse.jgit.api;version="[5.2.0,5.3.0)", | |||
org.eclipse.jgit.api.errors;version="[5.2.0,5.3.0)", | |||
@@ -63,6 +64,11 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", | |||
org.junit.rules;version="[4.12,5.0.0)", | |||
org.junit.runner;version="[4.12,5.0.0)", | |||
org.junit.runners;version="[4.12,5.0.0)", | |||
org.mockito;version="[2.13.0,3.0.0)", | |||
org.mockito.invocation;version="[2.13.0,3.0.0)", | |||
org.mockito.junit;version="[2.13.0,3.0.0)", | |||
org.mockito.stubbing;version="[2.13.0,3.0.0)", | |||
org.objenesis;version="[2.6.0,3.0.0)", | |||
org.slf4j;version="[1.7.0,2.0.0)" | |||
Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", | |||
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" |
@@ -89,6 +89,12 @@ | |||
<version>[1.1.0,2.0.0)</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.mockito</groupId> | |||
<artifactId>mockito-core</artifactId> | |||
<version>2.13.0</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.eclipse.jgit</groupId> | |||
<artifactId>org.eclipse.jgit</artifactId> |
@@ -0,0 +1,123 @@ | |||
/* | |||
* Copyright (C) 2018 Matthias Sohn <matthias.sohn@sap.com> | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Eclipse Distribution License v1.0 which | |||
* accompanies this distribution, is reproduced below, and is | |||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or | |||
* without modification, are permitted provided that the following | |||
* conditions are met: | |||
* | |||
* - Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* - Redistributions in binary form must reproduce the above | |||
* copyright notice, this list of conditions and the following | |||
* disclaimer in the documentation and/or other materials provided | |||
* with the distribution. | |||
* | |||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||
* names of its contributors may be used to endorse or promote | |||
* products derived from this software without specific prior | |||
* written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
package org.eclipse.jgit.transport.http; | |||
import static java.util.Arrays.asList; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import java.net.HttpURLConnection; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
public class JDKHttpConnectionTest { | |||
private Map<String, List<String>> headers = new HashMap<>(); | |||
private HttpURLConnection u; | |||
private JDKHttpConnection c; | |||
@Before | |||
public void setup() { | |||
u = mock(HttpURLConnection.class); | |||
c = new JDKHttpConnection(u); | |||
headers.put("ABC", asList("x")); | |||
} | |||
@Test | |||
public void testSingle() { | |||
when(u.getHeaderFields()).thenReturn(headers); | |||
assertValues("AbC", "x"); | |||
} | |||
@Test | |||
public void testMultiple1() { | |||
headers.put("abc", asList("a")); | |||
headers.put("aBC", asList("d", "e")); | |||
headers.put("ABc", Collections.emptyList()); | |||
headers.put("AbC", (List<String>) null); | |||
when(u.getHeaderFields()).thenReturn(headers); | |||
assertValues("AbC", "a", "d", "e", "x"); | |||
} | |||
@Test | |||
public void testMultiple2() { | |||
headers.put("ab", asList("y", "z", "z")); | |||
when(u.getHeaderFields()).thenReturn(headers); | |||
assertValues("ab", "z", "y", "z"); | |||
assertValues("abc", "x"); | |||
assertValues("aBc", "x"); | |||
assertValues("AbCd"); | |||
} | |||
@Test | |||
public void testCommaSeparatedList() { | |||
headers.put("abc", asList("a,b,c", "d")); | |||
when(u.getHeaderFields()).thenReturn(headers); | |||
assertValues("Abc", "a,b,c", "x", "d"); | |||
} | |||
private void assertValues(String key, String... values) { | |||
List<String> l = new LinkedList<>(); | |||
List<String> hf = c.getHeaderFields(key); | |||
if (hf != null) { | |||
l.addAll(hf); | |||
} | |||
for (String v : values) { | |||
if (!l.remove(v)) { | |||
fail("value " + v + " not found"); | |||
} | |||
} | |||
assertTrue("found unexpected entries " + l, l.isEmpty()); | |||
} | |||
} |
@@ -66,4 +66,12 @@ | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/transport/http/HttpConnection.java" type="org.eclipse.jgit.transport.http.HttpConnection"> | |||
<filter id="403804204"> | |||
<message_arguments> | |||
<message_argument value="org.eclipse.jgit.transport.http.HttpConnection"/> | |||
<message_argument value="getHeaderFields(String)"/> | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
</component> |
@@ -58,6 +58,8 @@ import javax.net.ssl.HostnameVerifier; | |||
import javax.net.ssl.KeyManager; | |||
import javax.net.ssl.TrustManager; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
/** | |||
* The interface of connections used during HTTP communication. This interface | |||
* is that subset of the interface exposed by {@link java.net.HttpURLConnection} | |||
@@ -139,7 +141,7 @@ public interface HttpConnection { | |||
String getResponseMessage() throws IOException; | |||
/** | |||
* Get list of header fields | |||
* Get map of header fields | |||
* | |||
* @see HttpURLConnection#getHeaderFields() | |||
* @return a Map of header fields | |||
@@ -225,7 +227,13 @@ public interface HttpConnection { | |||
InputStream getInputStream() throws IOException; | |||
/** | |||
* Get header field | |||
* Get header field. According to | |||
* {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC | |||
* 2616</a>} header field names are case insensitive. Header fields defined | |||
* as a comma separated list can have multiple header fields with the same | |||
* field name. This method only returns one of these header fields. If you | |||
* want the union of all values of all multiple header fields with the same | |||
* field name then use {@link #getHeaderFields(String)} | |||
* | |||
* @see HttpURLConnection#getHeaderField(String) | |||
* @param name | |||
@@ -233,7 +241,22 @@ public interface HttpConnection { | |||
* @return the value of the named header field, or <code>null</code> if | |||
* there is no such field in the header. | |||
*/ | |||
String getHeaderField(String name); | |||
String getHeaderField(@NonNull String name); | |||
/** | |||
* Get all values of given header field. According to | |||
* {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC | |||
* 2616</a>} header field names are case insensitive. Header fields defined | |||
* as a comma separated list can have multiple header fields with the same | |||
* field name. This method does not validate if the given header field is | |||
* defined as a comma separated list. | |||
* | |||
* @param name | |||
* the name of a header field. | |||
* @return the list of values of the named header field | |||
* @since 5.2 | |||
*/ | |||
List<String> getHeaderFields(@NonNull String name); | |||
/** | |||
* Get content length |
@@ -53,6 +53,7 @@ import java.net.URL; | |||
import java.security.KeyManagementException; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.security.SecureRandom; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Map; | |||
@@ -62,6 +63,7 @@ import javax.net.ssl.KeyManager; | |||
import javax.net.ssl.SSLContext; | |||
import javax.net.ssl.TrustManager; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
/** | |||
* A {@link org.eclipse.jgit.transport.http.HttpConnection} which simply | |||
* delegates every call to a {@link java.net.HttpURLConnection}. This is the | |||
@@ -72,6 +74,11 @@ import javax.net.ssl.TrustManager; | |||
public class JDKHttpConnection implements HttpConnection { | |||
HttpURLConnection wrappedUrlConnection; | |||
// used for mock testing | |||
JDKHttpConnection(HttpURLConnection urlConnection) { | |||
this.wrappedUrlConnection = urlConnection; | |||
} | |||
/** | |||
* Constructor for JDKHttpConnection. | |||
* | |||
@@ -170,10 +177,26 @@ public class JDKHttpConnection implements HttpConnection { | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String getHeaderField(String name) { | |||
public String getHeaderField(@NonNull String name) { | |||
return wrappedUrlConnection.getHeaderField(name); | |||
} | |||
@Override | |||
public List<String> getHeaderFields(@NonNull String name) { | |||
Map<String, List<String>> m = wrappedUrlConnection.getHeaderFields(); | |||
List<String> fields = mapValuesToListIgnoreCase(name, m); | |||
return fields; | |||
} | |||
private static List<String> mapValuesToListIgnoreCase(String keyName, | |||
Map<String, List<String>> m) { | |||
List<String> fields = new LinkedList<>(); | |||
m.entrySet().stream().filter(e -> keyName.equalsIgnoreCase(e.getKey())) | |||
.filter(e -> e.getValue() != null) | |||
.forEach(e -> fields.addAll(e.getValue())); | |||
return fields; | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public int getContentLength() { |