/*
 * Copyright (C) 2010, Google Inc.
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.eclipse.jgit.util;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Extra utilities to support usage of HTTP.
/** * Extra utilities to support usage of HTTP. */
public class HttpSupport { private final static Logger LOG = LoggerFactory .getLogger(HttpSupport.class);
The GET HTTP method.
/** The {@code GET} HTTP method. */
public static final String METHOD_GET = "GET"; //$NON-NLS-1$
The HEAD HTTP method.
Since:4.3
/** The {@code HEAD} HTTP method. * @since 4.3 */
public static final String METHOD_HEAD = "HEAD"; //$NON-NLS-1$
The POST HTTP method.
Since:4.3
/** The {@code POST} HTTP method. * @since 4.3 */
public static final String METHOD_PUT = "PUT"; //$NON-NLS-1$
The POST HTTP method.
/** The {@code POST} HTTP method. */
public static final String METHOD_POST = "POST"; //$NON-NLS-1$
The Cache-Control header.
/** The {@code Cache-Control} header. */
public static final String HDR_CACHE_CONTROL = "Cache-Control"; //$NON-NLS-1$
The Pragma header.
/** The {@code Pragma} header. */
public static final String HDR_PRAGMA = "Pragma"; //$NON-NLS-1$
The User-Agent header.
/** The {@code User-Agent} header. */
public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$
The Server header.
Since:4.0
/** * The {@code Server} header. * @since 4.0 */
public static final String HDR_SERVER = "Server"; //$NON-NLS-1$
The Date header.
/** The {@code Date} header. */
public static final String HDR_DATE = "Date"; //$NON-NLS-1$
The Expires header.
/** The {@code Expires} header. */
public static final String HDR_EXPIRES = "Expires"; //$NON-NLS-1$
The ETag header.
/** The {@code ETag} header. */
public static final String HDR_ETAG = "ETag"; //$NON-NLS-1$
The If-None-Match header.
/** The {@code If-None-Match} header. */
public static final String HDR_IF_NONE_MATCH = "If-None-Match"; //$NON-NLS-1$
The Last-Modified header.
/** The {@code Last-Modified} header. */
public static final String HDR_LAST_MODIFIED = "Last-Modified"; //$NON-NLS-1$
The If-Modified-Since header.
/** The {@code If-Modified-Since} header. */
public static final String HDR_IF_MODIFIED_SINCE = "If-Modified-Since"; //$NON-NLS-1$
The Accept header.
/** The {@code Accept} header. */
public static final String HDR_ACCEPT = "Accept"; //$NON-NLS-1$
The Content-Type header.
/** The {@code Content-Type} header. */
public static final String HDR_CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$
The Content-Length header.
/** The {@code Content-Length} header. */
public static final String HDR_CONTENT_LENGTH = "Content-Length"; //$NON-NLS-1$
The Content-Encoding header.
/** The {@code Content-Encoding} header. */
public static final String HDR_CONTENT_ENCODING = "Content-Encoding"; //$NON-NLS-1$
The Content-Range header.
/** The {@code Content-Range} header. */
public static final String HDR_CONTENT_RANGE = "Content-Range"; //$NON-NLS-1$
The Accept-Ranges header.
/** The {@code Accept-Ranges} header. */
public static final String HDR_ACCEPT_RANGES = "Accept-Ranges"; //$NON-NLS-1$
The If-Range header.
/** The {@code If-Range} header. */
public static final String HDR_IF_RANGE = "If-Range"; //$NON-NLS-1$
The Range header.
/** The {@code Range} header. */
public static final String HDR_RANGE = "Range"; //$NON-NLS-1$
The Accept-Encoding header.
/** The {@code Accept-Encoding} header. */
public static final String HDR_ACCEPT_ENCODING = "Accept-Encoding"; //$NON-NLS-1$
The Location header.
Since:4.7
/** * The {@code Location} header. * @since 4.7 */
public static final String HDR_LOCATION = "Location"; //$NON-NLS-1$
The gzip encoding value for HDR_ACCEPT_ENCODING.
/** The {@code gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. */
public static final String ENCODING_GZIP = "gzip"; //$NON-NLS-1$
The x-gzip encoding value for HDR_ACCEPT_ENCODING.
Since:4.6
/** * The {@code x-gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. * @since 4.6 */
public static final String ENCODING_X_GZIP = "x-gzip"; //$NON-NLS-1$
The standard text/plain MIME type.
/** The standard {@code text/plain} MIME type. */
public static final String TEXT_PLAIN = "text/plain"; //$NON-NLS-1$
The Authorization header.
/** The {@code Authorization} header. */
public static final String HDR_AUTHORIZATION = "Authorization"; //$NON-NLS-1$
The WWW-Authenticate header.
/** The {@code WWW-Authenticate} header. */
public static final String HDR_WWW_AUTHENTICATE = "WWW-Authenticate"; //$NON-NLS-1$
The Cookie header.
Since:5.4
/** * The {@code Cookie} header. * * @since 5.4 */
public static final String HDR_COOKIE = "Cookie"; //$NON-NLS-1$
The Set-Cookie header.
Since:5.4
/** * The {@code Set-Cookie} header. * * @since 5.4 */
public static final String HDR_SET_COOKIE = "Set-Cookie"; //$NON-NLS-1$
The Set-Cookie2 header.
Since:5.4
/** * The {@code Set-Cookie2} header. * * @since 5.4 */
public static final String HDR_SET_COOKIE2 = "Set-Cookie2"; //$NON-NLS-1$ private static Set<String> configuredHttpsProtocols;
URL encode a value string into an output buffer.
Params:
  • urlstr – the output buffer.
  • key – value which must be encoded to protected special characters.
/** * URL encode a value string into an output buffer. * * @param urlstr * the output buffer. * @param key * value which must be encoded to protected special characters. */
public static void encode(StringBuilder urlstr, String key) { if (key == null || key.length() == 0) return; try { urlstr.append(URLEncoder.encode(key, UTF_8.name())); } catch (UnsupportedEncodingException e) { throw new RuntimeException(JGitText.get().couldNotURLEncodeToUTF8, e); } }
Get the HTTP response code from the request.

Roughly the same as c.getResponseCode() but the ConnectException is translated to be more understandable.

Params:
  • c – connection the code should be obtained from.
Throws:
  • IOException – communications error prevented obtaining the response code.
Returns:r HTTP status code, usually 200 to indicate success. See HttpConnection for other defined constants.
Since:3.3
/** * Get the HTTP response code from the request. * <p> * Roughly the same as <code>c.getResponseCode()</code> but the * ConnectException is translated to be more understandable. * * @param c * connection the code should be obtained from. * @return r HTTP status code, usually 200 to indicate success. See * {@link org.eclipse.jgit.transport.http.HttpConnection} for other * defined constants. * @throws java.io.IOException * communications error prevented obtaining the response code. * @since 3.3 */
public static int response(HttpConnection c) throws IOException { try { return c.getResponseCode(); } catch (ConnectException ce) { final URL url = c.getURL(); final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$ // The standard J2SE error message is not very useful. // if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$ throw new ConnectException(MessageFormat.format(JGitText.get().connectionTimeOut, host)); throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$ } }
Get the HTTP response code from the request.

Roughly the same as c.getResponseCode() but the ConnectException is translated to be more understandable.

Params:
  • c – connection the code should be obtained from.
Throws:
  • IOException – communications error prevented obtaining the response code.
Returns:r HTTP status code, usually 200 to indicate success. See HttpConnection for other defined constants.
/** * Get the HTTP response code from the request. * <p> * Roughly the same as <code>c.getResponseCode()</code> but the * ConnectException is translated to be more understandable. * * @param c * connection the code should be obtained from. * @return r HTTP status code, usually 200 to indicate success. See * {@link org.eclipse.jgit.transport.http.HttpConnection} for other * defined constants. * @throws java.io.IOException * communications error prevented obtaining the response code. */
public static int response(java.net.HttpURLConnection c) throws IOException { try { return c.getResponseCode(); } catch (ConnectException ce) { final URL url = c.getURL(); final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$ // The standard J2SE error message is not very useful. // if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$ throw new ConnectException(MessageFormat.format( JGitText.get().connectionTimeOut, host)); throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$ } }
Extract a HTTP header from the response.
Params:
  • c – connection the header should be obtained from.
  • headerName – the header name
Throws:
  • IOException – communications error prevented obtaining the header.
Returns:the header value
Since:4.7
/** * Extract a HTTP header from the response. * * @param c * connection the header should be obtained from. * @param headerName * the header name * @return the header value * @throws java.io.IOException * communications error prevented obtaining the header. * @since 4.7 */
public static String responseHeader(final HttpConnection c, final String headerName) throws IOException { return c.getHeaderField(headerName); }
Determine the proxy server (if any) needed to obtain a URL.
Params:
  • proxySelector – proxy support for the caller.
  • u – location of the server caller wants to talk to.
Throws:
  • ConnectException – the proxy could not be computed as the supplied URL could not be read. This failure should never occur.
Returns:proxy to communicate with the supplied URL.
/** * Determine the proxy server (if any) needed to obtain a URL. * * @param proxySelector * proxy support for the caller. * @param u * location of the server caller wants to talk to. * @return proxy to communicate with the supplied URL. * @throws java.net.ConnectException * the proxy could not be computed as the supplied URL could not * be read. This failure should never occur. */
public static Proxy proxyFor(ProxySelector proxySelector, URL u) throws ConnectException { try { URI uri = new URI(u.getProtocol(), null, u.getHost(), u.getPort(), null, null, null); return proxySelector.select(uri).get(0); } catch (URISyntaxException e) { final ConnectException err; err = new ConnectException(MessageFormat.format(JGitText.get().cannotDetermineProxyFor, u)); err.initCause(e); throw err; } }
Disable SSL and hostname verification for given HTTP connection
Params:
Throws:
Since:4.3
/** * Disable SSL and hostname verification for given HTTP connection * * @param conn * a {@link org.eclipse.jgit.transport.http.HttpConnection} * object. * @throws java.io.IOException * @since 4.3 */
public static void disableSslVerify(HttpConnection conn) throws IOException { final TrustManager[] trustAllCerts = new TrustManager[] { new DummyX509TrustManager() }; try { conn.configure(null, trustAllCerts, null); conn.setHostnameVerifier(new DummyHostnameVerifier()); } catch (KeyManagementException | NoSuchAlgorithmException e) { throw new IOException(e.getMessage()); } } private static class DummyX509TrustManager implements X509TrustManager { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { // no check } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { // no check } } private static class DummyHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { // always accept return true; } }
Enables all supported TLS protocol versions on the socket given. If system property "https.protocols" is set, only protocols specified there are enabled.

This is primarily a mechanism to deal with using TLS on IBM JDK. IBM JDK returns sockets that support all TLS protocol versions but have only the one specified in the context enabled. Oracle or OpenJDK return sockets that have all available protocols enabled already, up to the one specified.

SSLContext.getInstance() OpenJDK IDM JDK
"TLS" Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)
Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)
Supported: TLSv1, TLSV1.1, TLSv1.2
Enabled: TLSv1
"TLSv1.2" Supported: TLSv1, TLSV1.1, TLSv1.2
Enabled: TLSv1, TLSV1.1, TLSv1.2
Supported: TLSv1, TLSV1.1, TLSv1.2
Enabled: TLSv1.2
Params:
  • socket – to configure
See Also:
Since:5.7
/** * Enables all supported TLS protocol versions on the socket given. If * system property "https.protocols" is set, only protocols specified there * are enabled. * <p> * This is primarily a mechanism to deal with using TLS on IBM JDK. IBM JDK * returns sockets that support all TLS protocol versions but have only the * one specified in the context enabled. Oracle or OpenJDK return sockets * that have all available protocols enabled already, up to the one * specified. * <p> * <table> * <tr> * <td>SSLContext.getInstance()</td> * <td>OpenJDK</td> * <td>IDM JDK</td> * </tr> * <tr> * <td>"TLS"</td> * <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br /> * Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)</td> * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> * Enabled: TLSv1</td> * </tr> * <tr> * <td>"TLSv1.2"</td> * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> * Enabled: TLSv1, TLSV1.1, TLSv1.2</td> * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> * Enabled: TLSv1.2</td> * </tr> * </table> * * @param socket * to configure * @see <a href= * "https://www.ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jsse2Docs/matchsslcontext_tls.html">Behavior * of SSLContext.getInstance("TLS") on IBM JDK</a> * @see <a href= * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization">Customizing * JSSE about https.protocols</a> * @since 5.7 */
public static void configureTLS(SSLSocket socket) { // 1. Enable all available TLS protocol versions Set<String> enabled = new LinkedHashSet<>( Arrays.asList(socket.getEnabledProtocols())); for (String s : socket.getSupportedProtocols()) { if (s.startsWith("TLS")) { //$NON-NLS-1$ enabled.add(s); } } // 2. Respect the https.protocols system property Set<String> configured = getConfiguredProtocols(); if (!configured.isEmpty()) { enabled.retainAll(configured); } if (!enabled.isEmpty()) { socket.setEnabledProtocols(enabled.toArray(new String[0])); } } private static Set<String> getConfiguredProtocols() { Set<String> result = configuredHttpsProtocols; if (result == null) { String configured = getProperty("https.protocols"); //$NON-NLS-1$ if (StringUtils.isEmptyOrNull(configured)) { result = Collections.emptySet(); } else { result = new LinkedHashSet<>( Arrays.asList(configured.split("\\s*,\\s*"))); //$NON-NLS-1$ } configuredHttpsProtocols = result; } return result; } private static String getProperty(String property) { try { return SystemReader.getInstance().getProperty(property); } catch (SecurityException e) { LOG.warn(JGitText.get().failedReadHttpsProtocols, e); return null; } }
Scan a RFC 7230 token as it appears in HTTP headers.
Params:
  • header – to scan in
  • from – index in header to start scanning at
Throws:
See Also:
Returns:the index after the token, that is, on the first non-token character or header.length
Since:5.10
/** * Scan a RFC 7230 token as it appears in HTTP headers. * * @param header * to scan in * @param from * index in {@code header} to start scanning at * @return the index after the token, that is, on the first non-token * character or {@code header.length} * @throws IndexOutOfBoundsException * if {@code from < 0} or {@code from > header.length()} * * @see <a href="https://tools.ietf.org/html/rfc7230#appendix-B">RFC 7230, * Appendix B: Collected Grammar; "token" production</a> * @since 5.10 */
public static int scanToken(String header, int from) { int length = header.length(); int i = from; if (i < 0 || i > length) { throw new IndexOutOfBoundsException(); } while (i < length) { char c = header.charAt(i); switch (c) { case '!': case '#': case '$': case '%': case '&': case '\'': case '*': case '+': case '-': case '.': case '^': case '_': case '`': case '|': case '~': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': i++; break; default: if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { i++; break; } return i; } } return i; } private HttpSupport() { // Utility class only. } }