TransportHttp.java

  1. /*
  2.  * Copyright (C) 2008, 2010 Google Inc.
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4.  * Copyright (C) 2013, Matthias Sohn <matthias.sohn@sap.com>
  5.  * Copyright (C) 2017, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
  6.  *
  7.  * This program and the accompanying materials are made available under the
  8.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  9.  * https://www.eclipse.org/org/documents/edl-v10.php.
  10.  *
  11.  * SPDX-License-Identifier: BSD-3-Clause
  12.  */

  13. package org.eclipse.jgit.transport;

  14. import static java.nio.charset.StandardCharsets.UTF_8;
  15. import static org.eclipse.jgit.lib.Constants.HEAD;
  16. import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES;
  17. import static org.eclipse.jgit.lib.Constants.INFO_HTTP_ALTERNATES;
  18. import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
  19. import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
  20. import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
  21. import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
  22. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
  23. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
  24. import static org.eclipse.jgit.util.HttpSupport.HDR_COOKIE;
  25. import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION;
  26. import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
  27. import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE;
  28. import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE2;
  29. import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
  30. import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
  31. import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
  32. import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;

  33. import java.io.BufferedInputStream;
  34. import java.io.BufferedReader;
  35. import java.io.FileNotFoundException;
  36. import java.io.IOException;
  37. import java.io.InputStream;
  38. import java.io.InputStreamReader;
  39. import java.io.InterruptedIOException;
  40. import java.io.OutputStream;
  41. import java.io.UnsupportedEncodingException;
  42. import java.net.HttpCookie;
  43. import java.net.MalformedURLException;
  44. import java.net.Proxy;
  45. import java.net.ProxySelector;
  46. import java.net.SocketException;
  47. import java.net.URI;
  48. import java.net.URISyntaxException;
  49. import java.net.URL;
  50. import java.net.URLDecoder;
  51. import java.nio.charset.StandardCharsets;
  52. import java.nio.file.InvalidPathException;
  53. import java.nio.file.Path;
  54. import java.nio.file.Paths;
  55. import java.security.GeneralSecurityException;
  56. import java.security.cert.CertPathBuilderException;
  57. import java.security.cert.CertPathValidatorException;
  58. import java.security.cert.CertificateException;
  59. import java.text.MessageFormat;
  60. import java.util.ArrayList;
  61. import java.util.Arrays;
  62. import java.util.Collection;
  63. import java.util.Collections;
  64. import java.util.EnumSet;
  65. import java.util.HashSet;
  66. import java.util.LinkedHashSet;
  67. import java.util.LinkedList;
  68. import java.util.List;
  69. import java.util.Locale;
  70. import java.util.Map;
  71. import java.util.Set;
  72. import java.util.TreeMap;
  73. import java.util.zip.GZIPInputStream;
  74. import java.util.zip.GZIPOutputStream;

  75. import javax.net.ssl.SSLHandshakeException;

  76. import org.eclipse.jgit.annotations.NonNull;
  77. import org.eclipse.jgit.errors.ConfigInvalidException;
  78. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  79. import org.eclipse.jgit.errors.NotSupportedException;
  80. import org.eclipse.jgit.errors.PackProtocolException;
  81. import org.eclipse.jgit.errors.TransportException;
  82. import org.eclipse.jgit.internal.JGitText;
  83. import org.eclipse.jgit.internal.storage.file.RefDirectory;
  84. import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
  85. import org.eclipse.jgit.internal.transport.http.NetscapeCookieFileCache;
  86. import org.eclipse.jgit.lib.Constants;
  87. import org.eclipse.jgit.lib.ObjectId;
  88. import org.eclipse.jgit.lib.ObjectIdRef;
  89. import org.eclipse.jgit.lib.ProgressMonitor;
  90. import org.eclipse.jgit.lib.Ref;
  91. import org.eclipse.jgit.lib.Repository;
  92. import org.eclipse.jgit.lib.StoredConfig;
  93. import org.eclipse.jgit.lib.SymbolicRef;
  94. import org.eclipse.jgit.transport.HttpAuthMethod.Type;
  95. import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
  96. import org.eclipse.jgit.transport.http.HttpConnection;
  97. import org.eclipse.jgit.transport.http.HttpConnectionFactory;
  98. import org.eclipse.jgit.transport.http.HttpConnectionFactory2;
  99. import org.eclipse.jgit.util.HttpSupport;
  100. import org.eclipse.jgit.util.IO;
  101. import org.eclipse.jgit.util.RawParseUtils;
  102. import org.eclipse.jgit.util.StringUtils;
  103. import org.eclipse.jgit.util.SystemReader;
  104. import org.eclipse.jgit.util.TemporaryBuffer;
  105. import org.eclipse.jgit.util.io.DisabledOutputStream;
  106. import org.eclipse.jgit.util.io.UnionInputStream;
  107. import org.slf4j.Logger;
  108. import org.slf4j.LoggerFactory;

  109. /**
  110.  * Transport over HTTP and FTP protocols.
  111.  * <p>
  112.  * If the transport is using HTTP and the remote HTTP service is Git-aware
  113.  * (speaks the "smart-http protocol") this client will automatically take
  114.  * advantage of the additional Git-specific HTTP extensions. If the remote
  115.  * service does not support these extensions, the client will degrade to direct
  116.  * file fetching.
  117.  * <p>
  118.  * If the remote (server side) repository does not have the specialized Git
  119.  * support, object files are retrieved directly through standard HTTP GET (or
  120.  * binary FTP GET) requests. This make it easy to serve a Git repository through
  121.  * a standard web host provider that does not offer specific support for Git.
  122.  *
  123.  * @see WalkFetchConnection
  124.  */
  125. public class TransportHttp extends HttpTransport implements WalkTransport,
  126.         PackTransport {

  127.     private static final Logger LOG = LoggerFactory
  128.             .getLogger(TransportHttp.class);

  129.     private static final String SVC_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$

  130.     private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$

  131.     private static final byte[] VERSION = "version" //$NON-NLS-1$
  132.             .getBytes(StandardCharsets.US_ASCII);

  133.     /**
  134.      * Accept-Encoding header in the HTTP request
  135.      * (https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html).
  136.      *
  137.      * @since 4.6
  138.      */
  139.     public enum AcceptEncoding {
  140.         /**
  141.          * Do not specify an Accept-Encoding header. In most servers this
  142.          * results in the content being transmitted as-is.
  143.          */
  144.         UNSPECIFIED,

  145.         /**
  146.          * Accept gzip content encoding.
  147.          */
  148.         GZIP
  149.     }

  150.     static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
  151.         private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$

  152.         private final Set<String> schemeSet = Collections
  153.                 .unmodifiableSet(new LinkedHashSet<>(Arrays
  154.                         .asList(schemeNames)));

  155.         @Override
  156.         public String getName() {
  157.             return JGitText.get().transportProtoHTTP;
  158.         }

  159.         @Override
  160.         public Set<String> getSchemes() {
  161.             return schemeSet;
  162.         }

  163.         @Override
  164.         public Set<URIishField> getRequiredFields() {
  165.             return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
  166.                     URIishField.PATH));
  167.         }

  168.         @Override
  169.         public Set<URIishField> getOptionalFields() {
  170.             return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
  171.                     URIishField.PASS, URIishField.PORT));
  172.         }

  173.         @Override
  174.         public int getDefaultPort() {
  175.             return 80;
  176.         }

  177.         @Override
  178.         public Transport open(URIish uri, Repository local, String remoteName)
  179.                 throws NotSupportedException {
  180.             return new TransportHttp(local, uri);
  181.         }

  182.         @Override
  183.         public Transport open(URIish uri) throws NotSupportedException {
  184.             return new TransportHttp(uri);
  185.         }
  186.     };

  187.     static final TransportProtocol PROTO_FTP = new TransportProtocol() {
  188.         @Override
  189.         public String getName() {
  190.             return JGitText.get().transportProtoFTP;
  191.         }

  192.         @Override
  193.         public Set<String> getSchemes() {
  194.             return Collections.singleton("ftp"); //$NON-NLS-1$
  195.         }

  196.         @Override
  197.         public Set<URIishField> getRequiredFields() {
  198.             return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
  199.                     URIishField.PATH));
  200.         }

  201.         @Override
  202.         public Set<URIishField> getOptionalFields() {
  203.             return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
  204.                     URIishField.PASS, URIishField.PORT));
  205.         }

  206.         @Override
  207.         public int getDefaultPort() {
  208.             return 21;
  209.         }

  210.         @Override
  211.         public Transport open(URIish uri, Repository local, String remoteName)
  212.                 throws NotSupportedException {
  213.             return new TransportHttp(local, uri);
  214.         }
  215.     };

  216.     /**
  217.      * The current URI we're talking to. The inherited (final) field
  218.      * {@link #uri} stores the original URI; {@code currentUri} may be different
  219.      * after redirects.
  220.      */
  221.     private URIish currentUri;

  222.     private URL baseUrl;

  223.     private URL objectsUrl;

  224.     private final HttpConfig http;

  225.     private final ProxySelector proxySelector;

  226.     private boolean useSmartHttp = true;

  227.     private HttpAuthMethod authMethod = HttpAuthMethod.Type.NONE.method(null);

  228.     private Map<String, String> headers;

  229.     private boolean sslVerify;

  230.     private boolean sslFailure = false;

  231.     private HttpConnectionFactory factory;

  232.     private HttpConnectionFactory2.GitSession gitSession;

  233.     private boolean factoryUsed;

  234.     /**
  235.      * All stored cookies bound to this repo (independent of the baseUrl)
  236.      */
  237.     private final NetscapeCookieFile cookieFile;

  238.     /**
  239.      * The cookies to be sent with each request to the given {@link #baseUrl}.
  240.      * Filtered view on top of {@link #cookieFile} where only cookies which
  241.      * apply to the current url are left. This set needs to be filtered for
  242.      * expired entries each time prior to sending them.
  243.      */
  244.     private final Set<HttpCookie> relevantCookies;

  245.     TransportHttp(Repository local, URIish uri)
  246.             throws NotSupportedException {
  247.         super(local, uri);
  248.         setURI(uri);
  249.         http = new HttpConfig(local.getConfig(), uri);
  250.         proxySelector = ProxySelector.getDefault();
  251.         sslVerify = http.isSslVerify();
  252.         cookieFile = getCookieFileFromConfig(http);
  253.         relevantCookies = filterCookies(cookieFile, baseUrl);
  254.         factory = HttpTransport.getConnectionFactory();
  255.     }

  256.     private URL toURL(URIish urish) throws MalformedURLException {
  257.         String uriString = urish.toString();
  258.         if (!uriString.endsWith("/")) { //$NON-NLS-1$
  259.             uriString += '/';
  260.         }
  261.         return new URL(uriString);
  262.     }

  263.     /**
  264.      * Set uri a {@link org.eclipse.jgit.transport.URIish} object.
  265.      *
  266.      * @param uri
  267.      *            a {@link org.eclipse.jgit.transport.URIish} object.
  268.      * @throws org.eclipse.jgit.errors.NotSupportedException
  269.      * @since 4.9
  270.      */
  271.     protected void setURI(URIish uri) throws NotSupportedException {
  272.         try {
  273.             currentUri = uri;
  274.             baseUrl = toURL(uri);
  275.             objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$
  276.         } catch (MalformedURLException e) {
  277.             throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
  278.         }
  279.     }

  280.     /**
  281.      * Create a minimal HTTP transport with default configuration values.
  282.      *
  283.      * @param uri
  284.      * @throws NotSupportedException
  285.      */
  286.     TransportHttp(URIish uri) throws NotSupportedException {
  287.         super(uri);
  288.         setURI(uri);
  289.         http = new HttpConfig(uri);
  290.         proxySelector = ProxySelector.getDefault();
  291.         sslVerify = http.isSslVerify();
  292.         cookieFile = getCookieFileFromConfig(http);
  293.         relevantCookies = filterCookies(cookieFile, baseUrl);
  294.         factory = HttpTransport.getConnectionFactory();
  295.     }

  296.     /**
  297.      * Toggle whether or not smart HTTP transport should be used.
  298.      * <p>
  299.      * This flag exists primarily to support backwards compatibility testing
  300.      * within a testing framework, there is no need to modify it in most
  301.      * applications.
  302.      *
  303.      * @param on
  304.      *            if {@code true} (default), smart HTTP is enabled.
  305.      */
  306.     public void setUseSmartHttp(boolean on) {
  307.         useSmartHttp = on;
  308.     }

  309.     @SuppressWarnings("resource") // Closed by caller
  310.     private FetchConnection getConnection(HttpConnection c, InputStream in,
  311.             String service, Collection<RefSpec> refSpecs,
  312.             String... additionalPatterns) throws IOException {
  313.         BaseConnection f;
  314.         if (isSmartHttp(c, service)) {
  315.             InputStream withMark = in.markSupported() ? in
  316.                     : new BufferedInputStream(in);
  317.             readSmartHeaders(withMark, service);
  318.             f = new SmartHttpFetchConnection(withMark, refSpecs,
  319.                     additionalPatterns);
  320.         } else {
  321.             // Assume this server doesn't support smart HTTP fetch
  322.             // and fall back on dumb object walking.
  323.             f = newDumbConnection(in);
  324.         }
  325.         f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
  326.         return (FetchConnection) f;
  327.     }

  328.     /**
  329.      * Sets the {@link HttpConnectionFactory} to be used by this
  330.      * {@link TransportHttp} instance.
  331.      * <p>
  332.      * If no factory is set explicitly, the {@link TransportHttp} instance uses
  333.      * the {@link HttpTransport#getConnectionFactory() globally defined
  334.      * factory}.
  335.      * </p>
  336.      *
  337.      * @param customFactory
  338.      *            the {@link HttpConnectionFactory} to use
  339.      * @throws IllegalStateException
  340.      *             if an HTTP/HTTPS connection has already been opened on this
  341.      *             {@link TransportHttp} instance
  342.      * @since 5.11
  343.      */
  344.     public void setHttpConnectionFactory(
  345.             @NonNull HttpConnectionFactory customFactory) {
  346.         if (factoryUsed) {
  347.             throw new IllegalStateException(JGitText.get().httpFactoryInUse);
  348.         }
  349.         factory = customFactory;
  350.     }

  351.     /**
  352.      * Retrieves the {@link HttpConnectionFactory} used by this
  353.      * {@link TransportHttp} instance.
  354.      *
  355.      * @return the {@link HttpConnectionFactory}
  356.      * @since 5.11
  357.      */
  358.     @NonNull
  359.     public HttpConnectionFactory getHttpConnectionFactory() {
  360.         return factory;
  361.     }

  362.     /**
  363.      * Sets preemptive Basic HTTP authentication. If the given {@code username}
  364.      * or {@code password} is empty or {@code null}, no preemptive
  365.      * authentication will be done. If {@code username} and {@code password} are
  366.      * set, they will override authority information from the URI
  367.      * ("user:password@").
  368.      * <p>
  369.      * If the connection encounters redirects, the pre-authentication will be
  370.      * cleared if the redirect goes to a different host.
  371.      * </p>
  372.      *
  373.      * @param username
  374.      *            to use
  375.      * @param password
  376.      *            to use
  377.      * @throws IllegalStateException
  378.      *             if an HTTP/HTTPS connection has already been opened on this
  379.      *             {@link TransportHttp} instance
  380.      * @since 5.11
  381.      */
  382.     public void setPreemptiveBasicAuthentication(String username,
  383.             String password) {
  384.         if (factoryUsed) {
  385.             throw new IllegalStateException(JGitText.get().httpPreAuthTooLate);
  386.         }
  387.         if (StringUtils.isEmptyOrNull(username)
  388.                 || StringUtils.isEmptyOrNull(password)) {
  389.             authMethod = authFromUri(currentUri);
  390.         } else {
  391.             HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null);
  392.             basic.authorize(username, password);
  393.             authMethod = basic;
  394.         }
  395.     }

  396.     /** {@inheritDoc} */
  397.     @Override
  398.     public FetchConnection openFetch() throws TransportException,
  399.             NotSupportedException {
  400.         return openFetch(Collections.emptyList());
  401.     }

  402.     @Override
  403.     public FetchConnection openFetch(Collection<RefSpec> refSpecs,
  404.             String... additionalPatterns)
  405.             throws NotSupportedException, TransportException {
  406.         final String service = SVC_UPLOAD_PACK;
  407.         try {
  408.             TransferConfig.ProtocolVersion gitProtocol = protocol;
  409.             if (gitProtocol == null) {
  410.                 gitProtocol = TransferConfig.ProtocolVersion.V2;
  411.             }
  412.             HttpConnection c = connect(service, gitProtocol);
  413.             try (InputStream in = openInputStream(c)) {
  414.                 return getConnection(c, in, service, refSpecs,
  415.                         additionalPatterns);
  416.             }
  417.         } catch (NotSupportedException | TransportException err) {
  418.             throw err;
  419.         } catch (IOException err) {
  420.             throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
  421.         }
  422.     }

  423.     private WalkFetchConnection newDumbConnection(InputStream in)
  424.             throws IOException, PackProtocolException {
  425.         HttpObjectDB d = new HttpObjectDB(objectsUrl);
  426.         Map<String, Ref> refs;
  427.         try (BufferedReader br = toBufferedReader(in)) {
  428.             refs = d.readAdvertisedImpl(br);
  429.         }

  430.         if (!refs.containsKey(HEAD)) {
  431.             // If HEAD was not published in the info/refs file (it usually
  432.             // is not there) download HEAD by itself as a loose file and do
  433.             // the resolution by hand.
  434.             //
  435.             HttpConnection conn = httpOpen(
  436.                     METHOD_GET,
  437.                     new URL(baseUrl, HEAD),
  438.                     AcceptEncoding.GZIP);
  439.             int status = HttpSupport.response(conn);
  440.             switch (status) {
  441.             case HttpConnection.HTTP_OK: {
  442.                 try (BufferedReader br = toBufferedReader(
  443.                         openInputStream(conn))) {
  444.                     String line = br.readLine();
  445.                     if (line != null && line.startsWith(RefDirectory.SYMREF)) {
  446.                         String target = line.substring(RefDirectory.SYMREF.length());
  447.                         Ref r = refs.get(target);
  448.                         if (r == null)
  449.                             r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
  450.                         r = new SymbolicRef(HEAD, r);
  451.                         refs.put(r.getName(), r);
  452.                     } else if (line != null && ObjectId.isId(line)) {
  453.                         Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
  454.                                 HEAD, ObjectId.fromString(line));
  455.                         refs.put(r.getName(), r);
  456.                     }
  457.                 }
  458.                 break;
  459.             }

  460.             case HttpConnection.HTTP_NOT_FOUND:
  461.                 break;

  462.             default:
  463.                 throw new TransportException(uri, MessageFormat.format(
  464.                         JGitText.get().cannotReadHEAD, Integer.valueOf(status),
  465.                         conn.getResponseMessage()));
  466.             }
  467.         }

  468.         WalkFetchConnection wfc = new WalkFetchConnection(this, d);
  469.         wfc.available(refs);
  470.         return wfc;
  471.     }

  472.     private BufferedReader toBufferedReader(InputStream in) {
  473.         return new BufferedReader(new InputStreamReader(in, UTF_8));
  474.     }

  475.     /** {@inheritDoc} */
  476.     @Override
  477.     public PushConnection openPush() throws NotSupportedException,
  478.             TransportException {
  479.         final String service = SVC_RECEIVE_PACK;
  480.         try {
  481.             final HttpConnection c = connect(service);
  482.             try (InputStream in = openInputStream(c)) {
  483.                 if (isSmartHttp(c, service)) {
  484.                     return smartPush(service, c, in);
  485.                 } else if (!useSmartHttp) {
  486.                     final String msg = JGitText.get().smartHTTPPushDisabled;
  487.                     throw new NotSupportedException(msg);

  488.                 } else {
  489.                     final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush;
  490.                     throw new NotSupportedException(msg);
  491.                 }
  492.             }
  493.         } catch (NotSupportedException | TransportException err) {
  494.             throw err;
  495.         } catch (IOException err) {
  496.             throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
  497.         }
  498.     }

  499.     private PushConnection smartPush(String service, HttpConnection c,
  500.             InputStream in) throws IOException, TransportException {
  501.         BufferedInputStream inBuf = new BufferedInputStream(in);
  502.         readSmartHeaders(inBuf, service);
  503.         SmartHttpPushConnection p = new SmartHttpPushConnection(inBuf);
  504.         p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
  505.         return p;
  506.     }

  507.     /** {@inheritDoc} */
  508.     @Override
  509.     public void close() {
  510.         if (gitSession != null) {
  511.             gitSession.close();
  512.             gitSession = null;
  513.         }
  514.     }

  515.     /**
  516.      * Set additional headers on the HTTP connection
  517.      *
  518.      * @param headers
  519.      *            a map of name:values that are to be set as headers on the HTTP
  520.      *            connection
  521.      * @since 3.4
  522.      */
  523.     public void setAdditionalHeaders(Map<String, String> headers) {
  524.         this.headers = headers;
  525.     }

  526.     private NoRemoteRepositoryException createNotFoundException(URIish u,
  527.             URL url, String msg) {
  528.         String text;
  529.         if (msg != null && !msg.isEmpty()) {
  530.             text = MessageFormat.format(JGitText.get().uriNotFoundWithMessage,
  531.                     url, msg);
  532.         } else {
  533.             text = MessageFormat.format(JGitText.get().uriNotFound, url);
  534.         }
  535.         return new NoRemoteRepositoryException(u, text);
  536.     }

  537.     private HttpAuthMethod authFromUri(URIish u) {
  538.         String user = u.getUser();
  539.         String pass = u.getPass();
  540.         if (user != null && pass != null) {
  541.             try {
  542.                 // User/password are _not_ application/x-www-form-urlencoded. In
  543.                 // particular the "+" sign would be replaced by a space.
  544.                 user = URLDecoder.decode(user.replace("+", "%2B"), //$NON-NLS-1$ //$NON-NLS-2$
  545.                         StandardCharsets.UTF_8.name());
  546.                 pass = URLDecoder.decode(pass.replace("+", "%2B"), //$NON-NLS-1$ //$NON-NLS-2$
  547.                         StandardCharsets.UTF_8.name());
  548.                 HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null);
  549.                 basic.authorize(user, pass);
  550.                 return basic;
  551.             } catch (IllegalArgumentException
  552.                     | UnsupportedEncodingException e) {
  553.                 LOG.warn(JGitText.get().httpUserInfoDecodeError, u);
  554.             }
  555.         }
  556.         return HttpAuthMethod.Type.NONE.method(null);
  557.     }

  558.     private HttpConnection connect(String service)
  559.             throws TransportException, NotSupportedException {
  560.         return connect(service, null);
  561.     }

  562.     private HttpConnection connect(String service,
  563.             TransferConfig.ProtocolVersion protocolVersion)
  564.             throws TransportException, NotSupportedException {
  565.         URL u = getServiceURL(service);
  566.         if (HttpAuthMethod.Type.NONE.equals(authMethod.getType())) {
  567.             authMethod = authFromUri(currentUri);
  568.         }
  569.         int authAttempts = 1;
  570.         int redirects = 0;
  571.         Collection<Type> ignoreTypes = null;
  572.         for (;;) {
  573.             try {
  574.                 final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP);
  575.                 if (useSmartHttp) {
  576.                     String exp = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
  577.                     conn.setRequestProperty(HDR_ACCEPT, exp + ", */*"); //$NON-NLS-1$
  578.                 } else {
  579.                     conn.setRequestProperty(HDR_ACCEPT, "*/*"); //$NON-NLS-1$
  580.                 }
  581.                 if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) {
  582.                     conn.setRequestProperty(
  583.                             GitProtocolConstants.PROTOCOL_HEADER,
  584.                             GitProtocolConstants.VERSION_2_REQUEST);
  585.                 }
  586.                 final int status = HttpSupport.response(conn);
  587.                 processResponseCookies(conn);
  588.                 switch (status) {
  589.                 case HttpConnection.HTTP_OK:
  590.                     // Check if HttpConnection did some authentication in the
  591.                     // background (e.g Kerberos/SPNEGO).
  592.                     // That may not work for streaming requests and jgit
  593.                     // explicit authentication would be required
  594.                     if (authMethod.getType() == HttpAuthMethod.Type.NONE
  595.                             && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
  596.                         authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
  597.                     return conn;

  598.                 case HttpConnection.HTTP_NOT_FOUND:
  599.                     throw createNotFoundException(uri, u,
  600.                             conn.getResponseMessage());

  601.                 case HttpConnection.HTTP_UNAUTHORIZED:
  602.                     authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
  603.                     if (authMethod.getType() == HttpAuthMethod.Type.NONE)
  604.                         throw new TransportException(uri, MessageFormat.format(
  605.                                 JGitText.get().authenticationNotSupported, uri));
  606.                     CredentialsProvider credentialsProvider = getCredentialsProvider();
  607.                     if (credentialsProvider == null)
  608.                         throw new TransportException(uri,
  609.                                 JGitText.get().noCredentialsProvider);
  610.                     if (authAttempts > 1)
  611.                         credentialsProvider.reset(currentUri);
  612.                     if (3 < authAttempts
  613.                             || !authMethod.authorize(currentUri,
  614.                                     credentialsProvider)) {
  615.                         throw new TransportException(uri,
  616.                                 JGitText.get().notAuthorized);
  617.                     }
  618.                     authAttempts++;
  619.                     continue;

  620.                 case HttpConnection.HTTP_FORBIDDEN:
  621.                     throw new TransportException(uri, MessageFormat.format(
  622.                             JGitText.get().serviceNotPermitted, baseUrl,
  623.                             service));

  624.                 case HttpConnection.HTTP_MOVED_PERM:
  625.                 case HttpConnection.HTTP_MOVED_TEMP:
  626.                 case HttpConnection.HTTP_SEE_OTHER:
  627.                 case HttpConnection.HTTP_11_MOVED_PERM:
  628.                 case HttpConnection.HTTP_11_MOVED_TEMP:
  629.                     // SEE_OTHER should actually never be sent by a git server,
  630.                     // and in general should occur only on POST requests. But it
  631.                     // doesn't hurt to accept it here as a redirect.
  632.                     if (http.getFollowRedirects() == HttpRedirectMode.FALSE) {
  633.                         throw new TransportException(uri,
  634.                                 MessageFormat.format(
  635.                                         JGitText.get().redirectsOff,
  636.                                         Integer.valueOf(status)));
  637.                     }
  638.                     URIish newUri = redirect(u,
  639.                             conn.getHeaderField(HDR_LOCATION),
  640.                             Constants.INFO_REFS, redirects++);
  641.                     setURI(newUri);
  642.                     u = getServiceURL(service);
  643.                     authAttempts = 1;
  644.                     break;
  645.                 default:
  646.                     String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$
  647.                     throw new TransportException(uri, err);
  648.                 }
  649.             } catch (NotSupportedException | TransportException e) {
  650.                 throw e;
  651.             } catch (InterruptedIOException e) {
  652.                 // Timeout!? Don't try other authentication methods.
  653.                 throw new TransportException(uri, MessageFormat.format(
  654.                         JGitText.get().connectionTimeOut, u.getHost()), e);
  655.             } catch (SocketException e) {
  656.                 // Nothing on other end, timeout, connection reset, ...
  657.                 throw new TransportException(uri,
  658.                         JGitText.get().connectionFailed, e);
  659.             } catch (SSLHandshakeException e) {
  660.                 handleSslFailure(e);
  661.                 continue; // Re-try
  662.             } catch (IOException e) {
  663.                 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
  664.                     if (ignoreTypes == null) {
  665.                         ignoreTypes = new HashSet<>();
  666.                     }

  667.                     ignoreTypes.add(authMethod.getType());

  668.                     // reset auth method & attempts for next authentication type
  669.                     authMethod = HttpAuthMethod.Type.NONE.method(null);
  670.                     authAttempts = 1;

  671.                     continue;
  672.                 }

  673.                 throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
  674.             }
  675.         }
  676.     }

  677.     void processResponseCookies(HttpConnection conn) {
  678.         if (cookieFile != null && http.getSaveCookies()) {
  679.             List<HttpCookie> foundCookies = new LinkedList<>();

  680.             List<String> cookieHeaderValues = conn
  681.                     .getHeaderFields(HDR_SET_COOKIE);
  682.             if (!cookieHeaderValues.isEmpty()) {
  683.                 foundCookies.addAll(
  684.                         extractCookies(HDR_SET_COOKIE, cookieHeaderValues));
  685.             }
  686.             cookieHeaderValues = conn.getHeaderFields(HDR_SET_COOKIE2);
  687.             if (!cookieHeaderValues.isEmpty()) {
  688.                 foundCookies.addAll(
  689.                         extractCookies(HDR_SET_COOKIE2, cookieHeaderValues));
  690.             }
  691.             if (!foundCookies.isEmpty()) {
  692.                 try {
  693.                     // update cookie lists with the newly received cookies!
  694.                     Set<HttpCookie> cookies = cookieFile.getCookies(false);
  695.                     cookies.addAll(foundCookies);
  696.                     cookieFile.write(baseUrl);
  697.                     relevantCookies.addAll(foundCookies);
  698.                 } catch (IOException | IllegalArgumentException
  699.                         | InterruptedException e) {
  700.                     LOG.warn(MessageFormat.format(
  701.                             JGitText.get().couldNotPersistCookies,
  702.                             cookieFile.getPath()), e);
  703.                 }
  704.             }
  705.         }
  706.     }

  707.     private List<HttpCookie> extractCookies(String headerKey,
  708.             List<String> headerValues) {
  709.         List<HttpCookie> foundCookies = new LinkedList<>();
  710.         for (String headerValue : headerValues) {
  711.             foundCookies
  712.                     .addAll(HttpCookie.parse(headerKey + ':' + headerValue));
  713.         }
  714.         // HttpCookies.parse(...) is only compliant with RFC 2965. Make it RFC
  715.         // 6265 compliant by applying the logic from
  716.         // https://tools.ietf.org/html/rfc6265#section-5.2.3
  717.         for (HttpCookie foundCookie : foundCookies) {
  718.             String domain = foundCookie.getDomain();
  719.             if (domain != null && domain.startsWith(".")) { //$NON-NLS-1$
  720.                 foundCookie.setDomain(domain.substring(1));
  721.             }
  722.         }
  723.         return foundCookies;
  724.     }

  725.     private static class CredentialItems {
  726.         CredentialItem.InformationalMessage message;

  727.         /** Trust the server for this git operation */
  728.         CredentialItem.YesNoType now;

  729.         /**
  730.          * Trust the server for all git operations from this repository; may be
  731.          * {@code null} if the transport was created via
  732.          * {@link #TransportHttp(URIish)}.
  733.          */
  734.         CredentialItem.YesNoType forRepo;

  735.         /** Always trust the server from now on. */
  736.         CredentialItem.YesNoType always;

  737.         public CredentialItem[] items() {
  738.             if (forRepo == null) {
  739.                 return new CredentialItem[] { message, now, always };
  740.             }
  741.             return new CredentialItem[] { message, now, forRepo, always };
  742.         }
  743.     }

  744.     private void handleSslFailure(Throwable e) throws TransportException {
  745.         if (sslFailure || !trustInsecureSslConnection(e.getCause())) {
  746.             throw new TransportException(uri,
  747.                     MessageFormat.format(
  748.                             JGitText.get().sslFailureExceptionMessage,
  749.                             currentUri.setPass(null)),
  750.                     e);
  751.         }
  752.         sslFailure = true;
  753.     }

  754.     private boolean trustInsecureSslConnection(Throwable cause) {
  755.         if (cause instanceof CertificateException
  756.                 || cause instanceof CertPathBuilderException
  757.                 || cause instanceof CertPathValidatorException) {
  758.             // Certificate expired or revoked, PKIX path building not
  759.             // possible, self-signed certificate, host does not match ...
  760.             CredentialsProvider provider = getCredentialsProvider();
  761.             if (provider != null) {
  762.                 CredentialItems trust = constructSslTrustItems(cause);
  763.                 CredentialItem[] items = trust.items();
  764.                 if (provider.supports(items)) {
  765.                     boolean answered = provider.get(uri, items);
  766.                     if (answered) {
  767.                         // Not canceled
  768.                         boolean trustNow = trust.now.getValue();
  769.                         boolean trustLocal = trust.forRepo != null
  770.                                 && trust.forRepo.getValue();
  771.                         boolean trustAlways = trust.always.getValue();
  772.                         if (trustNow || trustLocal || trustAlways) {
  773.                             sslVerify = false;
  774.                             if (trustAlways) {
  775.                                 updateSslVerifyUser(false);
  776.                             } else if (trustLocal) {
  777.                                 updateSslVerify(local.getConfig(), false);
  778.                             }
  779.                             return true;
  780.                         }
  781.                     }
  782.                 }
  783.             }
  784.         }
  785.         return false;
  786.     }

  787.     private CredentialItems constructSslTrustItems(Throwable cause) {
  788.         CredentialItems items = new CredentialItems();
  789.         String info = MessageFormat.format(JGitText.get().sslFailureInfo,
  790.                 currentUri.setPass(null));
  791.         String sslMessage = cause.getLocalizedMessage();
  792.         if (sslMessage == null) {
  793.             sslMessage = cause.toString();
  794.         }
  795.         sslMessage = MessageFormat.format(JGitText.get().sslFailureCause,
  796.                 sslMessage);
  797.         items.message = new CredentialItem.InformationalMessage(info + '\n'
  798.                 + sslMessage + '\n'
  799.                 + JGitText.get().sslFailureTrustExplanation);
  800.         items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow);
  801.         if (local != null) {
  802.             items.forRepo = new CredentialItem.YesNoType(
  803.                     MessageFormat.format(JGitText.get().sslTrustForRepo,
  804.                     local.getDirectory()));
  805.         }
  806.         items.always = new CredentialItem.YesNoType(
  807.                 JGitText.get().sslTrustAlways);
  808.         return items;
  809.     }

  810.     private void updateSslVerify(StoredConfig config, boolean value) {
  811.         // Since git uses the original URI for matching, we must also use the
  812.         // original URI and cannot use the current URI (which might be different
  813.         // after redirects).
  814.         String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$
  815.         int port = uri.getPort();
  816.         if (port > 0) {
  817.             uriPattern += ":" + port; //$NON-NLS-1$
  818.         }
  819.         config.setBoolean(HttpConfig.HTTP, uriPattern,
  820.                 HttpConfig.SSL_VERIFY_KEY, value);
  821.         try {
  822.             config.save();
  823.         } catch (IOException e) {
  824.             LOG.error(JGitText.get().sslVerifyCannotSave, e);
  825.         }
  826.     }

  827.     private void updateSslVerifyUser(boolean value) {
  828.         StoredConfig userConfig = null;
  829.         try {
  830.             userConfig = SystemReader.getInstance().getUserConfig();
  831.             updateSslVerify(userConfig, value);
  832.         } catch (IOException | ConfigInvalidException e) {
  833.             // Log it, but otherwise ignore here.
  834.             LOG.error(e.getMessage(), e);
  835.         }
  836.     }

  837.     private URIish redirect(URL currentUrl, String location, String checkFor,
  838.             int redirects)
  839.             throws TransportException {
  840.         if (location == null || location.isEmpty()) {
  841.             throw new TransportException(uri,
  842.                     MessageFormat.format(JGitText.get().redirectLocationMissing,
  843.                             baseUrl));
  844.         }
  845.         if (redirects >= http.getMaxRedirects()) {
  846.             throw new TransportException(uri,
  847.                     MessageFormat.format(JGitText.get().redirectLimitExceeded,
  848.                             Integer.valueOf(http.getMaxRedirects()), baseUrl,
  849.                             location));
  850.         }
  851.         try {
  852.             URI redirectTo = new URI(location);
  853.             // Reset authentication if the redirect has user/password info or
  854.             // if the host is different.
  855.             boolean resetAuth = !StringUtils
  856.                     .isEmptyOrNull(redirectTo.getUserInfo());
  857.             String currentHost = currentUrl.getHost();
  858.             redirectTo = currentUrl.toURI().resolve(redirectTo);
  859.             resetAuth = resetAuth || !currentHost.equals(redirectTo.getHost());
  860.             String redirected = redirectTo.toASCIIString();
  861.             if (!isValidRedirect(baseUrl, redirected, checkFor)) {
  862.                 throw new TransportException(uri,
  863.                         MessageFormat.format(JGitText.get().redirectBlocked,
  864.                                 baseUrl, redirected));
  865.             }
  866.             redirected = redirected.substring(0, redirected.indexOf(checkFor));
  867.             URIish result = new URIish(redirected);
  868.             if (resetAuth) {
  869.                 authMethod = HttpAuthMethod.Type.NONE.method(null);
  870.             }
  871.             if (LOG.isInfoEnabled()) {
  872.                 LOG.info(MessageFormat.format(JGitText.get().redirectHttp,
  873.                         uri.setPass(null),
  874.                         Integer.valueOf(redirects), baseUrl, result));
  875.             }
  876.             return result;
  877.         } catch (URISyntaxException e) {
  878.             throw new TransportException(uri,
  879.                     MessageFormat.format(JGitText.get().invalidRedirectLocation,
  880.                             baseUrl, location),
  881.                     e);
  882.         }
  883.     }

  884.     private boolean isValidRedirect(URL current, String next, String checkFor) {
  885.         // Protocols must be the same, or current is "http" and next "https". We
  886.         // do not follow redirects from https back to http.
  887.         String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT);
  888.         int schemeEnd = next.indexOf("://"); //$NON-NLS-1$
  889.         if (schemeEnd < 0) {
  890.             return false;
  891.         }
  892.         String newProtocol = next.substring(0, schemeEnd)
  893.                 .toLowerCase(Locale.ROOT);
  894.         if (!oldProtocol.equals(newProtocol)) {
  895.             if (!"https".equals(newProtocol)) { //$NON-NLS-1$
  896.                 return false;
  897.             }
  898.         }
  899.         // git allows only rewriting the root, i.e., everything before INFO_REFS
  900.         // or the service name
  901.         if (!next.contains(checkFor)) {
  902.             return false;
  903.         }
  904.         // Basically we should test here that whatever follows INFO_REFS is
  905.         // unchanged. But since we re-construct the query part
  906.         // anyway, it doesn't matter.
  907.         return true;
  908.     }

  909.     private URL getServiceURL(String service)
  910.             throws NotSupportedException {
  911.         try {
  912.             final StringBuilder b = new StringBuilder();
  913.             b.append(baseUrl);

  914.             if (b.charAt(b.length() - 1) != '/') {
  915.                 b.append('/');
  916.             }
  917.             b.append(Constants.INFO_REFS);

  918.             if (useSmartHttp) {
  919.                 b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$
  920.                 b.append("service="); //$NON-NLS-1$
  921.                 b.append(service);
  922.             }

  923.             return new URL(b.toString());
  924.         } catch (MalformedURLException e) {
  925.             throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
  926.         }
  927.     }

  928.     /**
  929.      * Open an HTTP connection.
  930.      *
  931.      * @param method HTTP request method
  932.      * @param u url of the HTTP connection
  933.      * @param acceptEncoding accept-encoding header option
  934.      * @return the HTTP connection
  935.      * @throws java.io.IOException
  936.      * @since 4.6
  937.      */
  938.     protected HttpConnection httpOpen(String method, URL u,
  939.             AcceptEncoding acceptEncoding) throws IOException {
  940.         if (method == null || u == null || acceptEncoding == null) {
  941.             throw new NullPointerException();
  942.         }

  943.         final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
  944.         factoryUsed = true;
  945.         HttpConnection conn = factory.create(u, proxy);

  946.         if (gitSession == null && (factory instanceof HttpConnectionFactory2)) {
  947.             gitSession = ((HttpConnectionFactory2) factory).newSession();
  948.         }
  949.         if (gitSession != null) {
  950.             try {
  951.                 gitSession.configure(conn, sslVerify);
  952.             } catch (GeneralSecurityException e) {
  953.                 throw new IOException(e.getMessage(), e);
  954.             }
  955.         } else if (!sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$
  956.             // Backwards compatibility
  957.             HttpSupport.disableSslVerify(conn);
  958.         }

  959.         // We must do our own redirect handling to implement git rules and to
  960.         // handle http->https redirects
  961.         conn.setInstanceFollowRedirects(false);

  962.         conn.setRequestMethod(method);
  963.         conn.setUseCaches(false);
  964.         if (acceptEncoding == AcceptEncoding.GZIP) {
  965.             conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
  966.         }
  967.         conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$
  968.         if (http.getUserAgent() != null) {
  969.             conn.setRequestProperty(HDR_USER_AGENT, http.getUserAgent());
  970.         } else if (UserAgent.get() != null) {
  971.             conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
  972.         }
  973.         int timeOut = getTimeout();
  974.         if (timeOut != -1) {
  975.             int effTimeOut = timeOut * 1000;
  976.             conn.setConnectTimeout(effTimeOut);
  977.             conn.setReadTimeout(effTimeOut);
  978.         }
  979.         addHeaders(conn, http.getExtraHeaders());
  980.         // set cookie header if necessary
  981.         if (!relevantCookies.isEmpty()) {
  982.             setCookieHeader(conn);
  983.         }

  984.         if (this.headers != null && !this.headers.isEmpty()) {
  985.             for (Map.Entry<String, String> entry : this.headers.entrySet()) {
  986.                 conn.setRequestProperty(entry.getKey(), entry.getValue());
  987.             }
  988.         }
  989.         authMethod.configureRequest(conn);
  990.         return conn;
  991.     }

  992.     /**
  993.      * Adds a list of header strings to the connection. Headers are expected to
  994.      * separate keys from values, i.e. "Key: Value". Headers without colon or
  995.      * key are ignored (and logged), as are headers with keys that are not RFC
  996.      * 7230 tokens or with non-ASCII values.
  997.      *
  998.      * @param conn
  999.      *            The target HttpConnection
  1000.      * @param headersToAdd
  1001.      *            A list of header strings
  1002.      */
  1003.     static void addHeaders(HttpConnection conn, List<String> headersToAdd) {
  1004.         for (String header : headersToAdd) {
  1005.             // Empty values are allowed according to
  1006.             // https://tools.ietf.org/html/rfc7230
  1007.             int colon = header.indexOf(':');
  1008.             String key = null;
  1009.             if (colon > 0) {
  1010.                 key = header.substring(0, colon).trim();
  1011.             }
  1012.             if (key == null || key.isEmpty()) {
  1013.                 LOG.warn(MessageFormat.format(
  1014.                         JGitText.get().invalidHeaderFormat, header));
  1015.             } else if (HttpSupport.scanToken(key, 0) != key.length()) {
  1016.                 LOG.warn(MessageFormat.format(JGitText.get().invalidHeaderKey,
  1017.                         header));
  1018.             } else {
  1019.                 String value = header.substring(colon + 1).trim();
  1020.                 if (!StandardCharsets.US_ASCII.newEncoder().canEncode(value)) {
  1021.                     LOG.warn(MessageFormat
  1022.                             .format(JGitText.get().invalidHeaderValue, header));
  1023.                 } else {
  1024.                     conn.setRequestProperty(key, value);
  1025.                 }
  1026.             }
  1027.         }
  1028.     }

  1029.     private void setCookieHeader(HttpConnection conn) {
  1030.         StringBuilder cookieHeaderValue = new StringBuilder();
  1031.         for (HttpCookie cookie : relevantCookies) {
  1032.             if (!cookie.hasExpired()) {
  1033.                 if (cookieHeaderValue.length() > 0) {
  1034.                     cookieHeaderValue.append(';');
  1035.                 }
  1036.                 cookieHeaderValue.append(cookie.toString());
  1037.             }
  1038.         }
  1039.         if (cookieHeaderValue.length() > 0) {
  1040.             conn.setRequestProperty(HDR_COOKIE, cookieHeaderValue.toString());
  1041.         }
  1042.     }

  1043.     final InputStream openInputStream(HttpConnection conn)
  1044.             throws IOException {
  1045.         InputStream input = conn.getInputStream();
  1046.         if (isGzipContent(conn))
  1047.             input = new GZIPInputStream(input);
  1048.         return input;
  1049.     }

  1050.     IOException wrongContentType(String expType, String actType) {
  1051.         final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType);
  1052.         return new TransportException(uri, why);
  1053.     }

  1054.     private static NetscapeCookieFile getCookieFileFromConfig(
  1055.             HttpConfig config) {
  1056.         if (!StringUtils.isEmptyOrNull(config.getCookieFile())) {
  1057.             try {
  1058.                 Path cookieFilePath = Paths.get(config.getCookieFile());
  1059.                 return NetscapeCookieFileCache.getInstance(config)
  1060.                         .getEntry(cookieFilePath);
  1061.             } catch (InvalidPathException e) {
  1062.                 LOG.warn(MessageFormat.format(
  1063.                         JGitText.get().couldNotReadCookieFile,
  1064.                         config.getCookieFile()), e);
  1065.             }
  1066.         }
  1067.         return null;
  1068.     }

  1069.     private static Set<HttpCookie> filterCookies(NetscapeCookieFile cookieFile,
  1070.             URL url) {
  1071.         if (cookieFile != null) {
  1072.             return filterCookies(cookieFile.getCookies(true), url);
  1073.         }
  1074.         return Collections.emptySet();
  1075.     }

  1076.     /**
  1077.      *
  1078.      * @param allCookies
  1079.      *            a list of cookies.
  1080.      * @param url
  1081.      *            the url for which to filter the list of cookies.
  1082.      * @return only the cookies from {@code allCookies} which are relevant (i.e.
  1083.      *         are not expired, have a matching domain, have a matching path and
  1084.      *         have a matching secure attribute)
  1085.      */
  1086.     private static Set<HttpCookie> filterCookies(Set<HttpCookie> allCookies,
  1087.             URL url) {
  1088.         Set<HttpCookie> filteredCookies = new HashSet<>();
  1089.         for (HttpCookie cookie : allCookies) {
  1090.             if (cookie.hasExpired()) {
  1091.                 continue;
  1092.             }
  1093.             if (!matchesCookieDomain(url.getHost(), cookie.getDomain())) {
  1094.                 continue;
  1095.             }
  1096.             if (!matchesCookiePath(url.getPath(), cookie.getPath())) {
  1097.                 continue;
  1098.             }
  1099.             if (cookie.getSecure() && !"https".equals(url.getProtocol())) { //$NON-NLS-1$
  1100.                 continue;
  1101.             }
  1102.             filteredCookies.add(cookie);
  1103.         }
  1104.         return filteredCookies;
  1105.     }

  1106.     /**
  1107.      *
  1108.      * The utility method to check whether a host name is in a cookie's domain
  1109.      * or not. Similar to {@link HttpCookie#domainMatches(String, String)} but
  1110.      * implements domain matching rules according to
  1111.      * <a href="https://tools.ietf.org/html/rfc6265#section-5.1.3">RFC 6265,
  1112.      * section 5.1.3</a> instead of the rules from
  1113.      * <a href="https://tools.ietf.org/html/rfc2965#section-3.3">RFC 2965,
  1114.      * section 3.3.1</a>.
  1115.      * <p>
  1116.      * The former rules are also used by libcurl internally.
  1117.      * <p>
  1118.      * The rules are as follows
  1119.      *
  1120.      * A string matches another domain string if at least one of the following
  1121.      * conditions holds:
  1122.      * <ul>
  1123.      * <li>The domain string and the string are identical. (Note that both the
  1124.      * domain string and the string will have been canonicalized to lower case
  1125.      * at this point.)</li>
  1126.      * <li>All of the following conditions hold
  1127.      * <ul>
  1128.      * <li>The domain string is a suffix of the string.</li>
  1129.      * <li>The last character of the string that is not included in the domain
  1130.      * string is a %x2E (".") character.</li>
  1131.      * <li>The string is a host name (i.e., not an IP address).</li>
  1132.      * </ul>
  1133.      * </li>
  1134.      * </ul>
  1135.      *
  1136.      * @param host
  1137.      *            the host to compare against the cookieDomain
  1138.      * @param cookieDomain
  1139.      *            the domain to compare against
  1140.      * @return {@code true} if they domain-match; {@code false} if not
  1141.      *
  1142.      * @see <a href= "https://tools.ietf.org/html/rfc6265#section-5.1.3">RFC
  1143.      *      6265, section 5.1.3 (Domain Matching)</a>
  1144.      * @see <a href=
  1145.      *      "https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8206092">JDK-8206092
  1146.      *      : HttpCookie.domainMatches() does not match to sub-sub-domain</a>
  1147.      */
  1148.     static boolean matchesCookieDomain(String host, String cookieDomain) {
  1149.         cookieDomain = cookieDomain.toLowerCase(Locale.ROOT);
  1150.         host = host.toLowerCase(Locale.ROOT);
  1151.         if (host.equals(cookieDomain)) {
  1152.             return true;
  1153.         }
  1154.         if (!host.endsWith(cookieDomain)) {
  1155.             return false;
  1156.         }
  1157.         return host.charAt(host.length() - cookieDomain.length() - 1) == '.';
  1158.     }

  1159.     /**
  1160.      * The utility method to check whether a path is matching a cookie path
  1161.      * domain or not. The rules are defined by
  1162.      * <a href="https://tools.ietf.org/html/rfc6265#section-5.1.4">RFC 6265,
  1163.      * section 5.1.4</a>:
  1164.      *
  1165.      * A request-path path-matches a given cookie-path if at least one of the
  1166.      * following conditions holds:
  1167.      * <ul>
  1168.      * <li>The cookie-path and the request-path are identical.</li>
  1169.      * <li>The cookie-path is a prefix of the request-path, and the last
  1170.      * character of the cookie-path is %x2F ("/").</li>
  1171.      * <li>The cookie-path is a prefix of the request-path, and the first
  1172.      * character of the request-path that is not included in the cookie- path is
  1173.      * a %x2F ("/") character.</li>
  1174.      * </ul>
  1175.      * @param path
  1176.      *            the path to check
  1177.      * @param cookiePath
  1178.      *            the cookie's path
  1179.      *
  1180.      * @return {@code true} if they path-match; {@code false} if not
  1181.      */
  1182.     static boolean matchesCookiePath(String path, String cookiePath) {
  1183.         if (cookiePath.equals(path)) {
  1184.             return true;
  1185.         }
  1186.         if (!cookiePath.endsWith("/")) { //$NON-NLS-1$
  1187.             cookiePath += "/"; //$NON-NLS-1$
  1188.         }
  1189.         return path.startsWith(cookiePath);
  1190.     }

  1191.     private boolean isSmartHttp(HttpConnection c, String service) {
  1192.         final String expType = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
  1193.         final String actType = c.getContentType();
  1194.         return expType.equals(actType);
  1195.     }

  1196.     private boolean isGzipContent(HttpConnection c) {
  1197.         return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
  1198.                 || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
  1199.     }

  1200.     private void readSmartHeaders(InputStream in, String service)
  1201.             throws IOException {
  1202.         // A smart protocol V0 reply will have a '#' after the first 4 bytes,
  1203.         // but a dumb reply cannot contain a '#' until after byte 41. Do a
  1204.         // quick check to make sure its a smart reply before we parse
  1205.         // as a pkt-line stream.
  1206.         //
  1207.         // There appears to be a confusion about this in protocol V2. Github
  1208.         // sends the # service line as a git (not http) header also when
  1209.         // protocol V2 is used. Gitlab also does so. JGit's UploadPack doesn't,
  1210.         // and thus Gerrit also does not.
  1211.         final byte[] magic = new byte[14];
  1212.         if (!in.markSupported()) {
  1213.             throw new TransportException(uri,
  1214.                     JGitText.get().inputStreamMustSupportMark);
  1215.         }
  1216.         in.mark(14);
  1217.         IO.readFully(in, magic, 0, magic.length);
  1218.         // Did we get 000dversion 2 or similar? (Canonical is 000eversion 2\n,
  1219.         // but JGit and thus Gerrit omits the \n.)
  1220.         if (Arrays.equals(Arrays.copyOfRange(magic, 4, 11), VERSION)
  1221.                 && magic[12] >= '1' && magic[12] <= '9') {
  1222.             // It's a smart server doing version 1 or greater, but not sending
  1223.             // the # service line header. Don't consume the version line.
  1224.             in.reset();
  1225.             return;
  1226.         }
  1227.         if (magic[4] != '#') {
  1228.             throw new TransportException(uri, MessageFormat.format(
  1229.                     JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
  1230.         }
  1231.         in.reset();
  1232.         final PacketLineIn pckIn = new PacketLineIn(in);
  1233.         final String exp = "# service=" + service; //$NON-NLS-1$
  1234.         final String act = pckIn.readString();
  1235.         if (!exp.equals(act)) {
  1236.             throw new TransportException(uri, MessageFormat.format(
  1237.                     JGitText.get().expectedGot, exp, act));
  1238.         }

  1239.         while (!PacketLineIn.isEnd(pckIn.readString())) {
  1240.             // for now, ignore the remaining header lines
  1241.         }
  1242.     }

  1243.     class HttpObjectDB extends WalkRemoteObjectDatabase {
  1244.         private final URL httpObjectsUrl;

  1245.         HttpObjectDB(URL b) {
  1246.             httpObjectsUrl = b;
  1247.         }

  1248.         @Override
  1249.         URIish getURI() {
  1250.             return new URIish(httpObjectsUrl);
  1251.         }

  1252.         @Override
  1253.         Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
  1254.             try {
  1255.                 return readAlternates(INFO_HTTP_ALTERNATES);
  1256.             } catch (FileNotFoundException err) {
  1257.                 // Fall through.
  1258.             }

  1259.             try {
  1260.                 return readAlternates(INFO_ALTERNATES);
  1261.             } catch (FileNotFoundException err) {
  1262.                 // Fall through.
  1263.             }

  1264.             return null;
  1265.         }

  1266.         @Override
  1267.         WalkRemoteObjectDatabase openAlternate(String location)
  1268.                 throws IOException {
  1269.             return new HttpObjectDB(new URL(httpObjectsUrl, location));
  1270.         }

  1271.         @Override
  1272.         BufferedReader openReader(String path) throws IOException {
  1273.             // Line oriented readable content is likely to compress well.
  1274.             // Request gzip encoding.
  1275.             InputStream is = open(path, AcceptEncoding.GZIP).in;
  1276.             return new BufferedReader(new InputStreamReader(is, UTF_8));
  1277.         }

  1278.         @Override
  1279.         Collection<String> getPackNames() throws IOException {
  1280.             final Collection<String> packs = new ArrayList<>();
  1281.             try (BufferedReader br = openReader(INFO_PACKS)) {
  1282.                 for (;;) {
  1283.                     final String s = br.readLine();
  1284.                     if (s == null || s.length() == 0)
  1285.                         break;
  1286.                     if (!s.startsWith("P pack-") || !s.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$
  1287.                         throw invalidAdvertisement(s);
  1288.                     packs.add(s.substring(2));
  1289.                 }
  1290.                 return packs;
  1291.             } catch (FileNotFoundException err) {
  1292.                 return packs;
  1293.             }
  1294.         }

  1295.         @Override
  1296.         FileStream open(String path) throws IOException {
  1297.             return open(path, AcceptEncoding.UNSPECIFIED);
  1298.         }

  1299.         FileStream open(String path, AcceptEncoding acceptEncoding)
  1300.                 throws IOException {
  1301.             final URL base = httpObjectsUrl;
  1302.             final URL u = new URL(base, path);
  1303.             final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding);
  1304.             switch (HttpSupport.response(c)) {
  1305.             case HttpConnection.HTTP_OK:
  1306.                 final InputStream in = openInputStream(c);
  1307.                 // If content is being gzipped and then transferred, the content
  1308.                 // length in the header is the zipped content length, not the
  1309.                 // actual content length.
  1310.                 if (!isGzipContent(c)) {
  1311.                     final int len = c.getContentLength();
  1312.                     return new FileStream(in, len);
  1313.                 }
  1314.                 return new FileStream(in);
  1315.             case HttpConnection.HTTP_NOT_FOUND:
  1316.                 throw new FileNotFoundException(u.toString());
  1317.             default:
  1318.                 throw new IOException(u.toString() + ": " //$NON-NLS-1$
  1319.                         + HttpSupport.response(c) + " " //$NON-NLS-1$
  1320.                         + c.getResponseMessage());
  1321.             }
  1322.         }

  1323.         Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
  1324.                 throws IOException, PackProtocolException {
  1325.             final TreeMap<String, Ref> avail = new TreeMap<>();
  1326.             for (;;) {
  1327.                 String line = br.readLine();
  1328.                 if (line == null)
  1329.                     break;

  1330.                 final int tab = line.indexOf('\t');
  1331.                 if (tab < 0)
  1332.                     throw invalidAdvertisement(line);

  1333.                 String name;
  1334.                 final ObjectId id;

  1335.                 name = line.substring(tab + 1);
  1336.                 id = ObjectId.fromString(line.substring(0, tab));
  1337.                 if (name.endsWith("^{}")) { //$NON-NLS-1$
  1338.                     name = name.substring(0, name.length() - 3);
  1339.                     final Ref prior = avail.get(name);
  1340.                     if (prior == null)
  1341.                         throw outOfOrderAdvertisement(name);

  1342.                     if (prior.getPeeledObjectId() != null)
  1343.                         throw duplicateAdvertisement(name + "^{}"); //$NON-NLS-1$

  1344.                     avail.put(name, new ObjectIdRef.PeeledTag(
  1345.                             Ref.Storage.NETWORK, name,
  1346.                             prior.getObjectId(), id));
  1347.                 } else {
  1348.                     Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
  1349.                             Ref.Storage.NETWORK, name, id));
  1350.                     if (prior != null)
  1351.                         throw duplicateAdvertisement(name);
  1352.                 }
  1353.             }
  1354.             return avail;
  1355.         }

  1356.         private PackProtocolException outOfOrderAdvertisement(String n) {
  1357.             return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
  1358.         }

  1359.         private PackProtocolException invalidAdvertisement(String n) {
  1360.             return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
  1361.         }

  1362.         private PackProtocolException duplicateAdvertisement(String n) {
  1363.             return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
  1364.         }

  1365.         @Override
  1366.         void close() {
  1367.             // We do not maintain persistent connections.
  1368.         }
  1369.     }

  1370.     class SmartHttpFetchConnection extends BasePackFetchConnection {
  1371.         private MultiRequestService svc;

  1372.         SmartHttpFetchConnection(InputStream advertisement)
  1373.                 throws TransportException {
  1374.             this(advertisement, Collections.emptyList());
  1375.         }

  1376.         SmartHttpFetchConnection(InputStream advertisement,
  1377.                 Collection<RefSpec> refSpecs, String... additionalPatterns)
  1378.                 throws TransportException {
  1379.             super(TransportHttp.this);
  1380.             statelessRPC = true;

  1381.             init(advertisement, DisabledOutputStream.INSTANCE);
  1382.             outNeedsEnd = false;
  1383.             if (!readAdvertisedRefs()) {
  1384.                 // Must be protocol V2
  1385.                 LongPollService service = new LongPollService(SVC_UPLOAD_PACK,
  1386.                         getProtocolVersion());
  1387.                 init(service.getInputStream(), service.getOutputStream());
  1388.                 lsRefs(refSpecs, additionalPatterns);
  1389.             }
  1390.         }

  1391.         @Override
  1392.         protected void doFetch(final ProgressMonitor monitor,
  1393.                 final Collection<Ref> want, final Set<ObjectId> have,
  1394.                 final OutputStream outputStream) throws TransportException {
  1395.             try {
  1396.                 svc = new MultiRequestService(SVC_UPLOAD_PACK,
  1397.                         getProtocolVersion());
  1398.                 init(svc.getInputStream(), svc.getOutputStream());
  1399.                 super.doFetch(monitor, want, have, outputStream);
  1400.             } finally {
  1401.                 svc = null;
  1402.             }
  1403.         }

  1404.         @Override
  1405.         protected void onReceivePack() {
  1406.             svc.finalRequest = true;
  1407.         }
  1408.     }

  1409.     class SmartHttpPushConnection extends BasePackPushConnection {
  1410.         SmartHttpPushConnection(InputStream advertisement)
  1411.                 throws TransportException {
  1412.             super(TransportHttp.this);
  1413.             statelessRPC = true;

  1414.             init(advertisement, DisabledOutputStream.INSTANCE);
  1415.             outNeedsEnd = false;
  1416.             readAdvertisedRefs();
  1417.         }

  1418.         @Override
  1419.         protected void doPush(final ProgressMonitor monitor,
  1420.                 final Map<String, RemoteRefUpdate> refUpdates,
  1421.                 OutputStream outputStream) throws TransportException {
  1422.             final Service svc = new MultiRequestService(SVC_RECEIVE_PACK,
  1423.                     getProtocolVersion());
  1424.             init(svc.getInputStream(), svc.getOutputStream());
  1425.             super.doPush(monitor, refUpdates, outputStream);
  1426.         }
  1427.     }

  1428.     /** Basic service for sending and receiving HTTP requests. */
  1429.     abstract class Service {
  1430.         protected final String serviceName;

  1431.         protected final String requestType;

  1432.         protected final String responseType;

  1433.         protected HttpConnection conn;

  1434.         protected HttpOutputStream out;

  1435.         protected final HttpExecuteStream execute;

  1436.         protected final TransferConfig.ProtocolVersion protocolVersion;

  1437.         final UnionInputStream in;

  1438.         Service(String serviceName,
  1439.                 TransferConfig.ProtocolVersion protocolVersion) {
  1440.             this.serviceName = serviceName;
  1441.             this.protocolVersion = protocolVersion;
  1442.             this.requestType = "application/x-" + serviceName + "-request"; //$NON-NLS-1$ //$NON-NLS-2$
  1443.             this.responseType = "application/x-" + serviceName + "-result"; //$NON-NLS-1$ //$NON-NLS-2$

  1444.             this.out = new HttpOutputStream();
  1445.             this.execute = new HttpExecuteStream();
  1446.             this.in = new UnionInputStream(execute);
  1447.         }

  1448.         void openStream() throws IOException {
  1449.             conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
  1450.                     AcceptEncoding.GZIP);
  1451.             conn.setInstanceFollowRedirects(false);
  1452.             conn.setDoOutput(true);
  1453.             conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
  1454.             conn.setRequestProperty(HDR_ACCEPT, responseType);
  1455.             if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) {
  1456.                 conn.setRequestProperty(GitProtocolConstants.PROTOCOL_HEADER,
  1457.                         GitProtocolConstants.VERSION_2_REQUEST);
  1458.             }
  1459.         }

  1460.         void sendRequest() throws IOException {
  1461.             // Try to compress the content, but only if that is smaller.
  1462.             TemporaryBuffer buf = new TemporaryBuffer.Heap(
  1463.                     http.getPostBuffer());
  1464.             try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) {
  1465.                 out.writeTo(gzip, null);
  1466.                 if (out.length() < buf.length())
  1467.                     buf = out;
  1468.             } catch (IOException err) {
  1469.                 // Most likely caused by overflowing the buffer, meaning
  1470.                 // its larger if it were compressed. Don't compress.
  1471.                 buf = out;
  1472.             }

  1473.             HttpAuthMethod authenticator = null;
  1474.             Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
  1475.             // Counts number of repeated authentication attempts using the same
  1476.             // authentication scheme
  1477.             int authAttempts = 1;
  1478.             int redirects = 0;
  1479.             for (;;) {
  1480.                 try {
  1481.                     // The very first time we will try with the authentication
  1482.                     // method used on the initial GET request. This is a hint
  1483.                     // only; it may fail. If so, we'll then re-try with proper
  1484.                     // 401 handling, going through the available authentication
  1485.                     // schemes.
  1486.                     openStream();
  1487.                     if (buf != out) {
  1488.                         conn.setRequestProperty(HDR_CONTENT_ENCODING,
  1489.                                 ENCODING_GZIP);
  1490.                     }
  1491.                     conn.setFixedLengthStreamingMode((int) buf.length());
  1492.                     try (OutputStream httpOut = conn.getOutputStream()) {
  1493.                         buf.writeTo(httpOut, null);
  1494.                     }

  1495.                     final int status = HttpSupport.response(conn);
  1496.                     switch (status) {
  1497.                     case HttpConnection.HTTP_OK:
  1498.                         // We're done.
  1499.                         return;

  1500.                     case HttpConnection.HTTP_NOT_FOUND:
  1501.                         throw createNotFoundException(uri, conn.getURL(),
  1502.                                 conn.getResponseMessage());

  1503.                     case HttpConnection.HTTP_FORBIDDEN:
  1504.                         throw new TransportException(uri,
  1505.                                 MessageFormat.format(
  1506.                                         JGitText.get().serviceNotPermitted,
  1507.                                         baseUrl, serviceName));

  1508.                     case HttpConnection.HTTP_MOVED_PERM:
  1509.                     case HttpConnection.HTTP_MOVED_TEMP:
  1510.                     case HttpConnection.HTTP_11_MOVED_PERM:
  1511.                     case HttpConnection.HTTP_11_MOVED_TEMP:
  1512.                         // SEE_OTHER after a POST doesn't make sense for a git
  1513.                         // server, so we don't handle it here and thus we'll
  1514.                         // report an error in openResponse() later on.
  1515.                         if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
  1516.                             // Let openResponse() issue an error
  1517.                             return;
  1518.                         }
  1519.                         currentUri = redirect(conn.getURL(),
  1520.                                 conn.getHeaderField(HDR_LOCATION),
  1521.                                 '/' + serviceName, redirects++);
  1522.                         try {
  1523.                             baseUrl = toURL(currentUri);
  1524.                         } catch (MalformedURLException e) {
  1525.                             throw new TransportException(uri,
  1526.                                     MessageFormat.format(
  1527.                                             JGitText.get().invalidRedirectLocation,
  1528.                                             baseUrl, currentUri),
  1529.                                     e);
  1530.                         }
  1531.                         continue;

  1532.                     case HttpConnection.HTTP_UNAUTHORIZED:
  1533.                         HttpAuthMethod nextMethod = HttpAuthMethod
  1534.                                 .scanResponse(conn, ignoreTypes);
  1535.                         switch (nextMethod.getType()) {
  1536.                         case NONE:
  1537.                             throw new TransportException(uri,
  1538.                                     MessageFormat.format(
  1539.                                             JGitText.get().authenticationNotSupported,
  1540.                                             conn.getURL()));
  1541.                         case NEGOTIATE:
  1542.                             // RFC 4559 states "When using the SPNEGO [...] with
  1543.                             // [...] POST, the authentication should be complete
  1544.                             // [...] before sending the user data." So in theory
  1545.                             // the initial GET should have been authenticated
  1546.                             // already. (Unless there was a redirect?)
  1547.                             //
  1548.                             // We try this only once:
  1549.                             ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
  1550.                             if (authenticator != null) {
  1551.                                 ignoreTypes.add(authenticator.getType());
  1552.                             }
  1553.                             authAttempts = 1;
  1554.                             // We only do the Kerberos part of SPNEGO, which
  1555.                             // requires only one round.
  1556.                             break;
  1557.                         default:
  1558.                             // DIGEST or BASIC. Let's be sure we ignore
  1559.                             // NEGOTIATE; if it was available, we have tried it
  1560.                             // before.
  1561.                             ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
  1562.                             if (authenticator == null || authenticator
  1563.                                     .getType() != nextMethod.getType()) {
  1564.                                 if (authenticator != null) {
  1565.                                     ignoreTypes.add(authenticator.getType());
  1566.                                 }
  1567.                                 authAttempts = 1;
  1568.                             }
  1569.                             break;
  1570.                         }
  1571.                         authMethod = nextMethod;
  1572.                         authenticator = nextMethod;
  1573.                         CredentialsProvider credentialsProvider = getCredentialsProvider();
  1574.                         if (credentialsProvider == null) {
  1575.                             throw new TransportException(uri,
  1576.                                     JGitText.get().noCredentialsProvider);
  1577.                         }
  1578.                         if (authAttempts > 1) {
  1579.                             credentialsProvider.reset(currentUri);
  1580.                         }
  1581.                         if (3 < authAttempts || !authMethod
  1582.                                 .authorize(currentUri, credentialsProvider)) {
  1583.                             throw new TransportException(uri,
  1584.                                     JGitText.get().notAuthorized);
  1585.                         }
  1586.                         authAttempts++;
  1587.                         continue;

  1588.                     default:
  1589.                         // Just return here; openResponse() will report an
  1590.                         // appropriate error.
  1591.                         return;
  1592.                     }
  1593.                 } catch (SSLHandshakeException e) {
  1594.                     handleSslFailure(e);
  1595.                     continue; // Re-try
  1596.                 } catch (SocketException | InterruptedIOException e) {
  1597.                     // Timeout!? Must propagate; don't try other authentication
  1598.                     // methods.
  1599.                     throw e;
  1600.                 } catch (IOException e) {
  1601.                     if (authenticator == null || authMethod
  1602.                             .getType() != HttpAuthMethod.Type.NONE) {
  1603.                         // Can happen for instance if the server advertises
  1604.                         // Negotiate, but the client isn't configured for
  1605.                         // Kerberos. The first time (authenticator == null) we
  1606.                         // must re-try even if the authMethod was NONE: this may
  1607.                         // occur if the server advertised NTLM on the GET
  1608.                         // and the HttpConnection managed to successfully
  1609.                         // authenticate under the hood with NTLM. We might not
  1610.                         // have picked this up on the GET's 200 response.
  1611.                         if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
  1612.                             ignoreTypes.add(authMethod.getType());
  1613.                         }
  1614.                         // Start over with the remaining available methods.
  1615.                         authMethod = HttpAuthMethod.Type.NONE.method(null);
  1616.                         authenticator = authMethod;
  1617.                         authAttempts = 1;
  1618.                         continue;
  1619.                     }
  1620.                     throw e;
  1621.                 }
  1622.             }
  1623.         }

  1624.         void openResponse() throws IOException {
  1625.             final int status = HttpSupport.response(conn);
  1626.             if (status != HttpConnection.HTTP_OK) {
  1627.                 throw new TransportException(uri, status + " " //$NON-NLS-1$
  1628.                         + conn.getResponseMessage());
  1629.             }

  1630.             final String contentType = conn.getContentType();
  1631.             if (!responseType.equals(contentType)) {
  1632.                 conn.getInputStream().close();
  1633.                 throw wrongContentType(responseType, contentType);
  1634.             }
  1635.         }

  1636.         HttpOutputStream getOutputStream() {
  1637.             return out;
  1638.         }

  1639.         InputStream getInputStream() {
  1640.             return in;
  1641.         }

  1642.         abstract void execute() throws IOException;

  1643.         class HttpExecuteStream extends InputStream {
  1644.             @Override
  1645.             public int read() throws IOException {
  1646.                 execute();
  1647.                 return -1;
  1648.             }

  1649.             @Override
  1650.             public int read(byte[] b, int off, int len) throws IOException {
  1651.                 execute();
  1652.                 return -1;
  1653.             }

  1654.             @Override
  1655.             public long skip(long n) throws IOException {
  1656.                 execute();
  1657.                 return 0;
  1658.             }
  1659.         }

  1660.         class HttpOutputStream extends TemporaryBuffer {
  1661.             HttpOutputStream() {
  1662.                 super(http.getPostBuffer());
  1663.             }

  1664.             @Override
  1665.             protected OutputStream overflow() throws IOException {
  1666.                 openStream();
  1667.                 conn.setChunkedStreamingMode(0);
  1668.                 return conn.getOutputStream();
  1669.             }
  1670.         }
  1671.     }

  1672.     /**
  1673.      * State required to speak multiple HTTP requests with the remote.
  1674.      * <p>
  1675.      * A service wrapper provides a normal looking InputStream and OutputStream
  1676.      * pair which are connected via HTTP to the named remote service. Writing to
  1677.      * the OutputStream is buffered until either the buffer overflows, or
  1678.      * reading from the InputStream occurs. If overflow occurs HTTP/1.1 and its
  1679.      * chunked transfer encoding is used to stream the request data to the
  1680.      * remote service. If the entire request fits in the memory buffer, the
  1681.      * older HTTP/1.0 standard and a fixed content length is used instead.
  1682.      * <p>
  1683.      * It is an error to attempt to read without there being outstanding data
  1684.      * ready for transmission on the OutputStream.
  1685.      * <p>
  1686.      * No state is preserved between write-read request pairs. The caller is
  1687.      * responsible for replaying state vector information as part of the request
  1688.      * data written to the OutputStream. Any session HTTP cookies may or may not
  1689.      * be preserved between requests, it is left up to the JVM's implementation
  1690.      * of the HTTP client.
  1691.      */
  1692.     class MultiRequestService extends Service {
  1693.         boolean finalRequest;

  1694.         MultiRequestService(String serviceName,
  1695.                 TransferConfig.ProtocolVersion protocolVersion) {
  1696.             super(serviceName, protocolVersion);
  1697.         }

  1698.         /** Keep opening send-receive pairs to the given URI. */
  1699.         @Override
  1700.         void execute() throws IOException {
  1701.             out.close();

  1702.             if (conn == null) {
  1703.                 if (out.length() == 0) {
  1704.                     // Request output hasn't started yet, but more data is being
  1705.                     // requested. If there is no request data buffered and the
  1706.                     // final request was already sent, do nothing to ensure the
  1707.                     // caller is shown EOF on the InputStream; otherwise an
  1708.                     // programming error has occurred within this module.
  1709.                     if (finalRequest)
  1710.                         return;
  1711.                     throw new TransportException(uri,
  1712.                             JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
  1713.                 }

  1714.                 sendRequest();
  1715.             }

  1716.             out.reset();

  1717.             openResponse();

  1718.             in.add(openInputStream(conn));
  1719.             if (!finalRequest)
  1720.                 in.add(execute);
  1721.             conn = null;
  1722.         }
  1723.     }

  1724.     /** Service for maintaining a single long-poll connection. */
  1725.     class LongPollService extends Service {

  1726.         LongPollService(String serviceName,
  1727.                 TransferConfig.ProtocolVersion protocolVersion) {
  1728.             super(serviceName, protocolVersion);
  1729.         }

  1730.         /** Only open one send-receive request. */
  1731.         @Override
  1732.         void execute() throws IOException {
  1733.             out.close();
  1734.             if (conn == null)
  1735.                 sendRequest();
  1736.             openResponse();
  1737.             in.add(openInputStream(conn));
  1738.         }
  1739.     }
  1740. }