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, Thomas Wolf <thomas.wolf@paranor.ch>
  6.  * and other copyright owners as documented in the project's IP log.
  7.  *
  8.  * This program and the accompanying materials are made available
  9.  * under the terms of the Eclipse Distribution License v1.0 which
  10.  * accompanies this distribution, is reproduced below, and is
  11.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  12.  *
  13.  * All rights reserved.
  14.  *
  15.  * Redistribution and use in source and binary forms, with or
  16.  * without modification, are permitted provided that the following
  17.  * conditions are met:
  18.  *
  19.  * - Redistributions of source code must retain the above copyright
  20.  *   notice, this list of conditions and the following disclaimer.
  21.  *
  22.  * - Redistributions in binary form must reproduce the above
  23.  *   copyright notice, this list of conditions and the following
  24.  *   disclaimer in the documentation and/or other materials provided
  25.  *   with the distribution.
  26.  *
  27.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  28.  *   names of its contributors may be used to endorse or promote
  29.  *   products derived from this software without specific prior
  30.  *   written permission.
  31.  *
  32.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  33.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  34.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  36.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  37.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  39.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  40.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  41.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  42.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  43.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  44.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45.  */

  46. package org.eclipse.jgit.transport;

  47. import static java.nio.charset.StandardCharsets.UTF_8;
  48. import static org.eclipse.jgit.lib.Constants.HEAD;
  49. import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
  50. import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
  51. import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
  52. import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
  53. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
  54. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
  55. import static org.eclipse.jgit.util.HttpSupport.HDR_COOKIE;
  56. import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION;
  57. import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
  58. import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE;
  59. import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE2;
  60. import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
  61. import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
  62. import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
  63. import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;

  64. import java.io.BufferedReader;
  65. import java.io.ByteArrayInputStream;
  66. import java.io.FileNotFoundException;
  67. import java.io.IOException;
  68. import java.io.InputStream;
  69. import java.io.InputStreamReader;
  70. import java.io.OutputStream;
  71. import java.net.HttpCookie;
  72. import java.net.MalformedURLException;
  73. import java.net.Proxy;
  74. import java.net.ProxySelector;
  75. import java.net.URISyntaxException;
  76. import java.net.URL;
  77. import java.nio.file.InvalidPathException;
  78. import java.nio.file.Path;
  79. import java.nio.file.Paths;
  80. import java.security.cert.CertPathBuilderException;
  81. import java.security.cert.CertPathValidatorException;
  82. import java.security.cert.CertificateException;
  83. import java.text.MessageFormat;
  84. import java.util.ArrayList;
  85. import java.util.Arrays;
  86. import java.util.Collection;
  87. import java.util.Collections;
  88. import java.util.EnumSet;
  89. import java.util.HashSet;
  90. import java.util.LinkedHashSet;
  91. import java.util.LinkedList;
  92. import java.util.List;
  93. import java.util.Locale;
  94. import java.util.Map;
  95. import java.util.Set;
  96. import java.util.TreeMap;
  97. import java.util.zip.GZIPInputStream;
  98. import java.util.zip.GZIPOutputStream;

  99. import javax.net.ssl.SSLHandshakeException;

  100. import org.eclipse.jgit.errors.ConfigInvalidException;
  101. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  102. import org.eclipse.jgit.errors.NotSupportedException;
  103. import org.eclipse.jgit.errors.PackProtocolException;
  104. import org.eclipse.jgit.errors.TransportException;
  105. import org.eclipse.jgit.internal.JGitText;
  106. import org.eclipse.jgit.internal.storage.file.RefDirectory;
  107. import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
  108. import org.eclipse.jgit.internal.transport.http.NetscapeCookieFileCache;
  109. import org.eclipse.jgit.lib.Constants;
  110. import org.eclipse.jgit.lib.ObjectId;
  111. import org.eclipse.jgit.lib.ObjectIdRef;
  112. import org.eclipse.jgit.lib.ProgressMonitor;
  113. import org.eclipse.jgit.lib.Ref;
  114. import org.eclipse.jgit.lib.Repository;
  115. import org.eclipse.jgit.lib.StoredConfig;
  116. import org.eclipse.jgit.lib.SymbolicRef;
  117. import org.eclipse.jgit.storage.file.FileBasedConfig;
  118. import org.eclipse.jgit.transport.HttpAuthMethod.Type;
  119. import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
  120. import org.eclipse.jgit.transport.http.HttpConnection;
  121. import org.eclipse.jgit.util.FS;
  122. import org.eclipse.jgit.util.HttpSupport;
  123. import org.eclipse.jgit.util.IO;
  124. import org.eclipse.jgit.util.RawParseUtils;
  125. import org.eclipse.jgit.util.StringUtils;
  126. import org.eclipse.jgit.util.SystemReader;
  127. import org.eclipse.jgit.util.TemporaryBuffer;
  128. import org.eclipse.jgit.util.io.DisabledOutputStream;
  129. import org.eclipse.jgit.util.io.UnionInputStream;
  130. import org.slf4j.Logger;
  131. import org.slf4j.LoggerFactory;

  132. /**
  133.  * Transport over HTTP and FTP protocols.
  134.  * <p>
  135.  * If the transport is using HTTP and the remote HTTP service is Git-aware
  136.  * (speaks the "smart-http protocol") this client will automatically take
  137.  * advantage of the additional Git-specific HTTP extensions. If the remote
  138.  * service does not support these extensions, the client will degrade to direct
  139.  * file fetching.
  140.  * <p>
  141.  * If the remote (server side) repository does not have the specialized Git
  142.  * support, object files are retrieved directly through standard HTTP GET (or
  143.  * binary FTP GET) requests. This make it easy to serve a Git repository through
  144.  * a standard web host provider that does not offer specific support for Git.
  145.  *
  146.  * @see WalkFetchConnection
  147.  */
  148. public class TransportHttp extends HttpTransport implements WalkTransport,
  149.         PackTransport {

  150.     private static final Logger LOG = LoggerFactory
  151.             .getLogger(TransportHttp.class);

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

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

  154.     /**
  155.      * Accept-Encoding header in the HTTP request
  156.      * (https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html).
  157.      *
  158.      * @since 4.6
  159.      */
  160.     public enum AcceptEncoding {
  161.         /**
  162.          * Do not specify an Accept-Encoding header. In most servers this
  163.          * results in the content being transmitted as-is.
  164.          */
  165.         UNSPECIFIED,

  166.         /**
  167.          * Accept gzip content encoding.
  168.          */
  169.         GZIP
  170.     }

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

  173.         private final Set<String> schemeSet = Collections
  174.                 .unmodifiableSet(new LinkedHashSet<>(Arrays
  175.                         .asList(schemeNames)));

  176.         @Override
  177.         public String getName() {
  178.             return JGitText.get().transportProtoHTTP;
  179.         }

  180.         @Override
  181.         public Set<String> getSchemes() {
  182.             return schemeSet;
  183.         }

  184.         @Override
  185.         public Set<URIishField> getRequiredFields() {
  186.             return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
  187.                     URIishField.PATH));
  188.         }

  189.         @Override
  190.         public Set<URIishField> getOptionalFields() {
  191.             return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
  192.                     URIishField.PASS, URIishField.PORT));
  193.         }

  194.         @Override
  195.         public int getDefaultPort() {
  196.             return 80;
  197.         }

  198.         @Override
  199.         public Transport open(URIish uri, Repository local, String remoteName)
  200.                 throws NotSupportedException {
  201.             return new TransportHttp(local, uri);
  202.         }

  203.         @Override
  204.         public Transport open(URIish uri) throws NotSupportedException {
  205.             return new TransportHttp(uri);
  206.         }
  207.     };

  208.     static final TransportProtocol PROTO_FTP = new TransportProtocol() {
  209.         @Override
  210.         public String getName() {
  211.             return JGitText.get().transportProtoFTP;
  212.         }

  213.         @Override
  214.         public Set<String> getSchemes() {
  215.             return Collections.singleton("ftp"); //$NON-NLS-1$
  216.         }

  217.         @Override
  218.         public Set<URIishField> getRequiredFields() {
  219.             return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
  220.                     URIishField.PATH));
  221.         }

  222.         @Override
  223.         public Set<URIishField> getOptionalFields() {
  224.             return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
  225.                     URIishField.PASS, URIishField.PORT));
  226.         }

  227.         @Override
  228.         public int getDefaultPort() {
  229.             return 21;
  230.         }

  231.         @Override
  232.         public Transport open(URIish uri, Repository local, String remoteName)
  233.                 throws NotSupportedException {
  234.             return new TransportHttp(local, uri);
  235.         }
  236.     };

  237.     /**
  238.      * The current URI we're talking to. The inherited (final) field
  239.      * {@link #uri} stores the original URI; {@code currentUri} may be different
  240.      * after redirects.
  241.      */
  242.     private URIish currentUri;

  243.     private URL baseUrl;

  244.     private URL objectsUrl;

  245.     private final HttpConfig http;

  246.     private final ProxySelector proxySelector;

  247.     private boolean useSmartHttp = true;

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

  249.     private Map<String, String> headers;

  250.     private boolean sslVerify;

  251.     private boolean sslFailure = false;

  252.     /**
  253.      * All stored cookies bound to this repo (independent of the baseUrl)
  254.      */
  255.     private final NetscapeCookieFile cookieFile;

  256.     /**
  257.      * The cookies to be sent with each request to the given {@link #baseUrl}.
  258.      * Filtered view on top of {@link #cookieFile} where only cookies which
  259.      * apply to the current url are left. This set needs to be filtered for
  260.      * expired entries each time prior to sending them.
  261.      */
  262.     private final Set<HttpCookie> relevantCookies;

  263.     TransportHttp(Repository local, URIish uri)
  264.             throws NotSupportedException {
  265.         super(local, uri);
  266.         setURI(uri);
  267.         http = new HttpConfig(local.getConfig(), uri);
  268.         proxySelector = ProxySelector.getDefault();
  269.         sslVerify = http.isSslVerify();
  270.         cookieFile = getCookieFileFromConfig(http);
  271.         relevantCookies = filterCookies(cookieFile, baseUrl);
  272.     }

  273.     private URL toURL(URIish urish) throws MalformedURLException {
  274.         String uriString = urish.toString();
  275.         if (!uriString.endsWith("/")) { //$NON-NLS-1$
  276.             uriString += '/';
  277.         }
  278.         return new URL(uriString);
  279.     }

  280.     /**
  281.      * Set uri a {@link org.eclipse.jgit.transport.URIish} object.
  282.      *
  283.      * @param uri
  284.      *            a {@link org.eclipse.jgit.transport.URIish} object.
  285.      * @throws org.eclipse.jgit.errors.NotSupportedException
  286.      * @since 4.9
  287.      */
  288.     protected void setURI(URIish uri) throws NotSupportedException {
  289.         try {
  290.             currentUri = uri;
  291.             baseUrl = toURL(uri);
  292.             objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$
  293.         } catch (MalformedURLException e) {
  294.             throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
  295.         }
  296.     }

  297.     /**
  298.      * Create a minimal HTTP transport with default configuration values.
  299.      *
  300.      * @param uri
  301.      * @throws NotSupportedException
  302.      */
  303.     TransportHttp(URIish uri) throws NotSupportedException {
  304.         super(uri);
  305.         setURI(uri);
  306.         http = new HttpConfig(uri);
  307.         proxySelector = ProxySelector.getDefault();
  308.         sslVerify = http.isSslVerify();
  309.         cookieFile = getCookieFileFromConfig(http);
  310.         relevantCookies = filterCookies(cookieFile, baseUrl);
  311.     }

  312.     /**
  313.      * Toggle whether or not smart HTTP transport should be used.
  314.      * <p>
  315.      * This flag exists primarily to support backwards compatibility testing
  316.      * within a testing framework, there is no need to modify it in most
  317.      * applications.
  318.      *
  319.      * @param on
  320.      *            if {@code true} (default), smart HTTP is enabled.
  321.      */
  322.     public void setUseSmartHttp(boolean on) {
  323.         useSmartHttp = on;
  324.     }

  325.     @SuppressWarnings("resource") // Closed by caller
  326.     private FetchConnection getConnection(HttpConnection c, InputStream in,
  327.             String service) throws IOException {
  328.         BaseConnection f;
  329.         if (isSmartHttp(c, service)) {
  330.             readSmartHeaders(in, service);
  331.             f = new SmartHttpFetchConnection(in);
  332.         } else {
  333.             // Assume this server doesn't support smart HTTP fetch
  334.             // and fall back on dumb object walking.
  335.             f = newDumbConnection(in);
  336.         }
  337.         f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
  338.         return (FetchConnection) f;
  339.     }

  340.     /** {@inheritDoc} */
  341.     @Override
  342.     public FetchConnection openFetch() throws TransportException,
  343.             NotSupportedException {
  344.         final String service = SVC_UPLOAD_PACK;
  345.         try {
  346.             final HttpConnection c = connect(service);
  347.             try (InputStream in = openInputStream(c)) {
  348.                 return getConnection(c, in, service);
  349.             }
  350.         } catch (NotSupportedException | TransportException err) {
  351.             throw err;
  352.         } catch (IOException err) {
  353.             throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
  354.         }
  355.     }

  356.     private WalkFetchConnection newDumbConnection(InputStream in)
  357.             throws IOException, PackProtocolException {
  358.         HttpObjectDB d = new HttpObjectDB(objectsUrl);
  359.         Map<String, Ref> refs;
  360.         try (BufferedReader br = toBufferedReader(in)) {
  361.             refs = d.readAdvertisedImpl(br);
  362.         }

  363.         if (!refs.containsKey(HEAD)) {
  364.             // If HEAD was not published in the info/refs file (it usually
  365.             // is not there) download HEAD by itself as a loose file and do
  366.             // the resolution by hand.
  367.             //
  368.             HttpConnection conn = httpOpen(
  369.                     METHOD_GET,
  370.                     new URL(baseUrl, HEAD),
  371.                     AcceptEncoding.GZIP);
  372.             int status = HttpSupport.response(conn);
  373.             switch (status) {
  374.             case HttpConnection.HTTP_OK: {
  375.                 try (BufferedReader br = toBufferedReader(
  376.                         openInputStream(conn))) {
  377.                     String line = br.readLine();
  378.                     if (line != null && line.startsWith(RefDirectory.SYMREF)) {
  379.                         String target = line.substring(RefDirectory.SYMREF.length());
  380.                         Ref r = refs.get(target);
  381.                         if (r == null)
  382.                             r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
  383.                         r = new SymbolicRef(HEAD, r);
  384.                         refs.put(r.getName(), r);
  385.                     } else if (line != null && ObjectId.isId(line)) {
  386.                         Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
  387.                                 HEAD, ObjectId.fromString(line));
  388.                         refs.put(r.getName(), r);
  389.                     }
  390.                 }
  391.                 break;
  392.             }

  393.             case HttpConnection.HTTP_NOT_FOUND:
  394.                 break;

  395.             default:
  396.                 throw new TransportException(uri, MessageFormat.format(
  397.                         JGitText.get().cannotReadHEAD, Integer.valueOf(status),
  398.                         conn.getResponseMessage()));
  399.             }
  400.         }

  401.         WalkFetchConnection wfc = new WalkFetchConnection(this, d);
  402.         wfc.available(refs);
  403.         return wfc;
  404.     }

  405.     private BufferedReader toBufferedReader(InputStream in) {
  406.         return new BufferedReader(new InputStreamReader(in, UTF_8));
  407.     }

  408.     /** {@inheritDoc} */
  409.     @Override
  410.     public PushConnection openPush() throws NotSupportedException,
  411.             TransportException {
  412.         final String service = SVC_RECEIVE_PACK;
  413.         try {
  414.             final HttpConnection c = connect(service);
  415.             try (InputStream in = openInputStream(c)) {
  416.                 if (isSmartHttp(c, service)) {
  417.                     return smartPush(service, c, in);
  418.                 } else if (!useSmartHttp) {
  419.                     final String msg = JGitText.get().smartHTTPPushDisabled;
  420.                     throw new NotSupportedException(msg);

  421.                 } else {
  422.                     final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush;
  423.                     throw new NotSupportedException(msg);
  424.                 }
  425.             }
  426.         } catch (NotSupportedException | TransportException err) {
  427.             throw err;
  428.         } catch (IOException err) {
  429.             throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
  430.         }
  431.     }

  432.     private PushConnection smartPush(String service, HttpConnection c,
  433.             InputStream in) throws IOException, TransportException {
  434.         readSmartHeaders(in, service);
  435.         SmartHttpPushConnection p = new SmartHttpPushConnection(in);
  436.         p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
  437.         return p;
  438.     }

  439.     /** {@inheritDoc} */
  440.     @Override
  441.     public void close() {
  442.         // No explicit connections are maintained.
  443.     }

  444.     /**
  445.      * Set additional headers on the HTTP connection
  446.      *
  447.      * @param headers
  448.      *            a map of name:values that are to be set as headers on the HTTP
  449.      *            connection
  450.      * @since 3.4
  451.      */
  452.     public void setAdditionalHeaders(Map<String, String> headers) {
  453.         this.headers = headers;
  454.     }

  455.     private NoRemoteRepositoryException createNotFoundException(URIish u,
  456.             URL url, String msg) {
  457.         String text;
  458.         if (msg != null && !msg.isEmpty()) {
  459.             text = MessageFormat.format(JGitText.get().uriNotFoundWithMessage,
  460.                     url, msg);
  461.         } else {
  462.             text = MessageFormat.format(JGitText.get().uriNotFound, url);
  463.         }
  464.         return new NoRemoteRepositoryException(u, text);
  465.     }

  466.     private HttpConnection connect(String service)
  467.             throws TransportException, NotSupportedException {
  468.         URL u = getServiceURL(service);
  469.         int authAttempts = 1;
  470.         int redirects = 0;
  471.         Collection<Type> ignoreTypes = null;
  472.         for (;;) {
  473.             try {
  474.                 final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP);
  475.                 if (useSmartHttp) {
  476.                     String exp = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
  477.                     conn.setRequestProperty(HDR_ACCEPT, exp + ", */*"); //$NON-NLS-1$
  478.                 } else {
  479.                     conn.setRequestProperty(HDR_ACCEPT, "*/*"); //$NON-NLS-1$
  480.                 }
  481.                 final int status = HttpSupport.response(conn);
  482.                 processResponseCookies(conn);
  483.                 switch (status) {
  484.                 case HttpConnection.HTTP_OK:
  485.                     // Check if HttpConnection did some authentication in the
  486.                     // background (e.g Kerberos/SPNEGO).
  487.                     // That may not work for streaming requests and jgit
  488.                     // explicit authentication would be required
  489.                     if (authMethod.getType() == HttpAuthMethod.Type.NONE
  490.                             && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
  491.                         authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
  492.                     return conn;

  493.                 case HttpConnection.HTTP_NOT_FOUND:
  494.                     throw createNotFoundException(uri, u,
  495.                             conn.getResponseMessage());

  496.                 case HttpConnection.HTTP_UNAUTHORIZED:
  497.                     authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
  498.                     if (authMethod.getType() == HttpAuthMethod.Type.NONE)
  499.                         throw new TransportException(uri, MessageFormat.format(
  500.                                 JGitText.get().authenticationNotSupported, uri));
  501.                     CredentialsProvider credentialsProvider = getCredentialsProvider();
  502.                     if (credentialsProvider == null)
  503.                         throw new TransportException(uri,
  504.                                 JGitText.get().noCredentialsProvider);
  505.                     if (authAttempts > 1)
  506.                         credentialsProvider.reset(currentUri);
  507.                     if (3 < authAttempts
  508.                             || !authMethod.authorize(currentUri,
  509.                                     credentialsProvider)) {
  510.                         throw new TransportException(uri,
  511.                                 JGitText.get().notAuthorized);
  512.                     }
  513.                     authAttempts++;
  514.                     continue;

  515.                 case HttpConnection.HTTP_FORBIDDEN:
  516.                     throw new TransportException(uri, MessageFormat.format(
  517.                             JGitText.get().serviceNotPermitted, baseUrl,
  518.                             service));

  519.                 case HttpConnection.HTTP_MOVED_PERM:
  520.                 case HttpConnection.HTTP_MOVED_TEMP:
  521.                 case HttpConnection.HTTP_SEE_OTHER:
  522.                 case HttpConnection.HTTP_11_MOVED_TEMP:
  523.                     // SEE_OTHER should actually never be sent by a git server,
  524.                     // and in general should occur only on POST requests. But it
  525.                     // doesn't hurt to accept it here as a redirect.
  526.                     if (http.getFollowRedirects() == HttpRedirectMode.FALSE) {
  527.                         throw new TransportException(uri,
  528.                                 MessageFormat.format(
  529.                                         JGitText.get().redirectsOff,
  530.                                         Integer.valueOf(status)));
  531.                     }
  532.                     URIish newUri = redirect(conn.getHeaderField(HDR_LOCATION),
  533.                             Constants.INFO_REFS, redirects++);
  534.                     setURI(newUri);
  535.                     u = getServiceURL(service);
  536.                     authAttempts = 1;
  537.                     break;
  538.                 default:
  539.                     String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$
  540.                     throw new TransportException(uri, err);
  541.                 }
  542.             } catch (NotSupportedException | TransportException e) {
  543.                 throw e;
  544.             } catch (SSLHandshakeException e) {
  545.                 handleSslFailure(e);
  546.                 continue; // Re-try
  547.             } catch (IOException e) {
  548.                 if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
  549.                     if (ignoreTypes == null) {
  550.                         ignoreTypes = new HashSet<>();
  551.                     }

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

  553.                     // reset auth method & attempts for next authentication type
  554.                     authMethod = HttpAuthMethod.Type.NONE.method(null);
  555.                     authAttempts = 1;

  556.                     continue;
  557.                 }

  558.                 throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
  559.             }
  560.         }
  561.     }

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

  565.             List<String> cookieHeaderValues = conn
  566.                     .getHeaderFields(HDR_SET_COOKIE);
  567.             if (!cookieHeaderValues.isEmpty()) {
  568.                 foundCookies.addAll(
  569.                         extractCookies(HDR_SET_COOKIE, cookieHeaderValues));
  570.             }
  571.             cookieHeaderValues = conn.getHeaderFields(HDR_SET_COOKIE2);
  572.             if (!cookieHeaderValues.isEmpty()) {
  573.                 foundCookies.addAll(
  574.                         extractCookies(HDR_SET_COOKIE2, cookieHeaderValues));
  575.             }
  576.             if (!foundCookies.isEmpty()) {
  577.                 try {
  578.                     // update cookie lists with the newly received cookies!
  579.                     Set<HttpCookie> cookies = cookieFile.getCookies(false);
  580.                     cookies.addAll(foundCookies);
  581.                     cookieFile.write(baseUrl);
  582.                     relevantCookies.addAll(foundCookies);
  583.                 } catch (IOException | IllegalArgumentException
  584.                         | InterruptedException e) {
  585.                     LOG.warn(MessageFormat.format(
  586.                             JGitText.get().couldNotPersistCookies,
  587.                             cookieFile.getPath()), e);
  588.                 }
  589.             }
  590.         }
  591.     }

  592.     private List<HttpCookie> extractCookies(String headerKey,
  593.             List<String> headerValues) {
  594.         List<HttpCookie> foundCookies = new LinkedList<>();
  595.         for (String headerValue : headerValues) {
  596.             foundCookies
  597.                     .addAll(HttpCookie.parse(headerKey + ':' + headerValue));
  598.         }
  599.         // HttpCookies.parse(...) is only compliant with RFC 2965. Make it RFC
  600.         // 6265 compliant by applying the logic from
  601.         // https://tools.ietf.org/html/rfc6265#section-5.2.3
  602.         for (HttpCookie foundCookie : foundCookies) {
  603.             String domain = foundCookie.getDomain();
  604.             if (domain != null && domain.startsWith(".")) { //$NON-NLS-1$
  605.                 foundCookie.setDomain(domain.substring(1));
  606.             }
  607.         }
  608.         return foundCookies;
  609.     }

  610.     private static class CredentialItems {
  611.         CredentialItem.InformationalMessage message;

  612.         /** Trust the server for this git operation */
  613.         CredentialItem.YesNoType now;

  614.         /**
  615.          * Trust the server for all git operations from this repository; may be
  616.          * {@code null} if the transport was created via
  617.          * {@link #TransportHttp(URIish)}.
  618.          */
  619.         CredentialItem.YesNoType forRepo;

  620.         /** Always trust the server from now on. */
  621.         CredentialItem.YesNoType always;

  622.         public CredentialItem[] items() {
  623.             if (forRepo == null) {
  624.                 return new CredentialItem[] { message, now, always };
  625.             } else {
  626.                 return new CredentialItem[] { message, now, forRepo, always };
  627.             }
  628.         }
  629.     }

  630.     private void handleSslFailure(Throwable e) throws TransportException {
  631.         if (sslFailure || !trustInsecureSslConnection(e.getCause())) {
  632.             throw new TransportException(uri,
  633.                     MessageFormat.format(
  634.                             JGitText.get().sslFailureExceptionMessage,
  635.                             currentUri.setPass(null)),
  636.                     e);
  637.         }
  638.         sslFailure = true;
  639.     }

  640.     private boolean trustInsecureSslConnection(Throwable cause) {
  641.         if (cause instanceof CertificateException
  642.                 || cause instanceof CertPathBuilderException
  643.                 || cause instanceof CertPathValidatorException) {
  644.             // Certificate expired or revoked, PKIX path building not
  645.             // possible, self-signed certificate, host does not match ...
  646.             CredentialsProvider provider = getCredentialsProvider();
  647.             if (provider != null) {
  648.                 CredentialItems trust = constructSslTrustItems(cause);
  649.                 CredentialItem[] items = trust.items();
  650.                 if (provider.supports(items)) {
  651.                     boolean answered = provider.get(uri, items);
  652.                     if (answered) {
  653.                         // Not canceled
  654.                         boolean trustNow = trust.now.getValue();
  655.                         boolean trustLocal = trust.forRepo != null
  656.                                 && trust.forRepo.getValue();
  657.                         boolean trustAlways = trust.always.getValue();
  658.                         if (trustNow || trustLocal || trustAlways) {
  659.                             sslVerify = false;
  660.                             if (trustAlways) {
  661.                                 updateSslVerifyUser(false);
  662.                             } else if (trustLocal) {
  663.                                 updateSslVerify(local.getConfig(), false);
  664.                             }
  665.                             return true;
  666.                         }
  667.                     }
  668.                 }
  669.             }
  670.         }
  671.         return false;
  672.     }

  673.     private CredentialItems constructSslTrustItems(Throwable cause) {
  674.         CredentialItems items = new CredentialItems();
  675.         String info = MessageFormat.format(JGitText.get().sslFailureInfo,
  676.                 currentUri.setPass(null));
  677.         String sslMessage = cause.getLocalizedMessage();
  678.         if (sslMessage == null) {
  679.             sslMessage = cause.toString();
  680.         }
  681.         sslMessage = MessageFormat.format(JGitText.get().sslFailureCause,
  682.                 sslMessage);
  683.         items.message = new CredentialItem.InformationalMessage(info + '\n'
  684.                 + sslMessage + '\n'
  685.                 + JGitText.get().sslFailureTrustExplanation);
  686.         items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow);
  687.         if (local != null) {
  688.             items.forRepo = new CredentialItem.YesNoType(
  689.                     MessageFormat.format(JGitText.get().sslTrustForRepo,
  690.                     local.getDirectory()));
  691.         }
  692.         items.always = new CredentialItem.YesNoType(
  693.                 JGitText.get().sslTrustAlways);
  694.         return items;
  695.     }

  696.     private void updateSslVerify(StoredConfig config, boolean value) {
  697.         // Since git uses the original URI for matching, we must also use the
  698.         // original URI and cannot use the current URI (which might be different
  699.         // after redirects).
  700.         String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$
  701.         int port = uri.getPort();
  702.         if (port > 0) {
  703.             uriPattern += ":" + port; //$NON-NLS-1$
  704.         }
  705.         config.setBoolean(HttpConfig.HTTP, uriPattern,
  706.                 HttpConfig.SSL_VERIFY_KEY, value);
  707.         try {
  708.             config.save();
  709.         } catch (IOException e) {
  710.             LOG.error(JGitText.get().sslVerifyCannotSave, e);
  711.         }
  712.     }

  713.     private void updateSslVerifyUser(boolean value) {
  714.         FileBasedConfig userConfig = SystemReader.getInstance()
  715.                 .openUserConfig(null, FS.DETECTED);
  716.         try {
  717.             userConfig.load();
  718.             updateSslVerify(userConfig, value);
  719.         } catch (IOException | ConfigInvalidException e) {
  720.             // Log it, but otherwise ignore here.
  721.             LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
  722.                     userConfig.getFile().getAbsolutePath(), e));
  723.         }
  724.     }

  725.     private URIish redirect(String location, String checkFor, int redirects)
  726.             throws TransportException {
  727.         if (location == null || location.isEmpty()) {
  728.             throw new TransportException(uri,
  729.                     MessageFormat.format(JGitText.get().redirectLocationMissing,
  730.                             baseUrl));
  731.         }
  732.         if (redirects >= http.getMaxRedirects()) {
  733.             throw new TransportException(uri,
  734.                     MessageFormat.format(JGitText.get().redirectLimitExceeded,
  735.                             Integer.valueOf(http.getMaxRedirects()), baseUrl,
  736.                             location));
  737.         }
  738.         try {
  739.             if (!isValidRedirect(baseUrl, location, checkFor)) {
  740.                 throw new TransportException(uri,
  741.                         MessageFormat.format(JGitText.get().redirectBlocked,
  742.                                 baseUrl, location));
  743.             }
  744.             location = location.substring(0, location.indexOf(checkFor));
  745.             URIish result = new URIish(location);
  746.             if (LOG.isInfoEnabled()) {
  747.                 LOG.info(MessageFormat.format(JGitText.get().redirectHttp,
  748.                         uri.setPass(null),
  749.                         Integer.valueOf(redirects), baseUrl, result));
  750.             }
  751.             return result;
  752.         } catch (URISyntaxException e) {
  753.             throw new TransportException(uri,
  754.                     MessageFormat.format(JGitText.get().invalidRedirectLocation,
  755.                             baseUrl, location),
  756.                     e);
  757.         }
  758.     }

  759.     private boolean isValidRedirect(URL current, String next, String checkFor) {
  760.         // Protocols must be the same, or current is "http" and next "https". We
  761.         // do not follow redirects from https back to http.
  762.         String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT);
  763.         int schemeEnd = next.indexOf("://"); //$NON-NLS-1$
  764.         if (schemeEnd < 0) {
  765.             return false;
  766.         }
  767.         String newProtocol = next.substring(0, schemeEnd)
  768.                 .toLowerCase(Locale.ROOT);
  769.         if (!oldProtocol.equals(newProtocol)) {
  770.             if (!"https".equals(newProtocol)) { //$NON-NLS-1$
  771.                 return false;
  772.             }
  773.         }
  774.         // git allows only rewriting the root, i.e., everything before INFO_REFS
  775.         // or the service name
  776.         if (!next.contains(checkFor)) {
  777.             return false;
  778.         }
  779.         // Basically we should test here that whatever follows INFO_REFS is
  780.         // unchanged. But since we re-construct the query part
  781.         // anyway, it doesn't matter.
  782.         return true;
  783.     }

  784.     private URL getServiceURL(String service)
  785.             throws NotSupportedException {
  786.         try {
  787.             final StringBuilder b = new StringBuilder();
  788.             b.append(baseUrl);

  789.             if (b.charAt(b.length() - 1) != '/') {
  790.                 b.append('/');
  791.             }
  792.             b.append(Constants.INFO_REFS);

  793.             if (useSmartHttp) {
  794.                 b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$
  795.                 b.append("service="); //$NON-NLS-1$
  796.                 b.append(service);
  797.             }

  798.             return new URL(b.toString());
  799.         } catch (MalformedURLException e) {
  800.             throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
  801.         }
  802.     }

  803.     /**
  804.      * Open an HTTP connection.
  805.      *
  806.      * @param method HTTP request method
  807.      * @param u url of the HTTP connection
  808.      * @param acceptEncoding accept-encoding header option
  809.      * @return the HTTP connection
  810.      * @throws java.io.IOException
  811.      * @since 4.6
  812.      */
  813.     protected HttpConnection httpOpen(String method, URL u,
  814.             AcceptEncoding acceptEncoding) throws IOException {
  815.         if (method == null || u == null || acceptEncoding == null) {
  816.             throw new NullPointerException();
  817.         }

  818.         final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
  819.         HttpConnection conn = connectionFactory.create(u, proxy);

  820.         if (!sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$
  821.             HttpSupport.disableSslVerify(conn);
  822.         }

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

  826.         conn.setRequestMethod(method);
  827.         conn.setUseCaches(false);
  828.         if (acceptEncoding == AcceptEncoding.GZIP) {
  829.             conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
  830.         }
  831.         conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$
  832.         if (UserAgent.get() != null) {
  833.             conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
  834.         }
  835.         int timeOut = getTimeout();
  836.         if (timeOut != -1) {
  837.             int effTimeOut = timeOut * 1000;
  838.             conn.setConnectTimeout(effTimeOut);
  839.             conn.setReadTimeout(effTimeOut);
  840.         }
  841.         // set cookie header if necessary
  842.         if (!relevantCookies.isEmpty()) {
  843.             setCookieHeader(conn);
  844.         }

  845.         if (this.headers != null && !this.headers.isEmpty()) {
  846.             for (Map.Entry<String, String> entry : this.headers.entrySet()) {
  847.                 conn.setRequestProperty(entry.getKey(), entry.getValue());
  848.             }
  849.         }
  850.         authMethod.configureRequest(conn);
  851.         return conn;
  852.     }

  853.     private void setCookieHeader(HttpConnection conn) {
  854.         StringBuilder cookieHeaderValue = new StringBuilder();
  855.         for (HttpCookie cookie : relevantCookies) {
  856.             if (!cookie.hasExpired()) {
  857.                 if (cookieHeaderValue.length() > 0) {
  858.                     cookieHeaderValue.append(';');
  859.                 }
  860.                 cookieHeaderValue.append(cookie.toString());
  861.             }
  862.         }
  863.         if (cookieHeaderValue.length() > 0) {
  864.             conn.setRequestProperty(HDR_COOKIE, cookieHeaderValue.toString());
  865.         }
  866.     }

  867.     final InputStream openInputStream(HttpConnection conn)
  868.             throws IOException {
  869.         InputStream input = conn.getInputStream();
  870.         if (isGzipContent(conn))
  871.             input = new GZIPInputStream(input);
  872.         return input;
  873.     }

  874.     IOException wrongContentType(String expType, String actType) {
  875.         final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType);
  876.         return new TransportException(uri, why);
  877.     }

  878.     private static NetscapeCookieFile getCookieFileFromConfig(
  879.             HttpConfig config) {
  880.         if (!StringUtils.isEmptyOrNull(config.getCookieFile())) {
  881.             try {
  882.                 Path cookieFilePath = Paths.get(config.getCookieFile());
  883.                 return NetscapeCookieFileCache.getInstance(config)
  884.                         .getEntry(cookieFilePath);
  885.             } catch (InvalidPathException e) {
  886.                 LOG.warn(MessageFormat.format(
  887.                         JGitText.get().couldNotReadCookieFile,
  888.                         config.getCookieFile()), e);
  889.             }
  890.         }
  891.         return null;
  892.     }

  893.     private static Set<HttpCookie> filterCookies(NetscapeCookieFile cookieFile,
  894.             URL url) {
  895.         if (cookieFile != null) {
  896.             return filterCookies(cookieFile.getCookies(true), url);
  897.         }
  898.         return Collections.emptySet();
  899.     }

  900.     /**
  901.      *
  902.      * @param allCookies
  903.      *            a list of cookies.
  904.      * @param url
  905.      *            the url for which to filter the list of cookies.
  906.      * @return only the cookies from {@code allCookies} which are relevant (i.e.
  907.      *         are not expired, have a matching domain, have a matching path and
  908.      *         have a matching secure attribute)
  909.      */
  910.     private static Set<HttpCookie> filterCookies(Set<HttpCookie> allCookies,
  911.             URL url) {
  912.         Set<HttpCookie> filteredCookies = new HashSet<>();
  913.         for (HttpCookie cookie : allCookies) {
  914.             if (cookie.hasExpired()) {
  915.                 continue;
  916.             }
  917.             if (!matchesCookieDomain(url.getHost(), cookie.getDomain())) {
  918.                 continue;
  919.             }
  920.             if (!matchesCookiePath(url.getPath(), cookie.getPath())) {
  921.                 continue;
  922.             }
  923.             if (cookie.getSecure() && !"https".equals(url.getProtocol())) { //$NON-NLS-1$
  924.                 continue;
  925.             }
  926.             filteredCookies.add(cookie);
  927.         }
  928.         return filteredCookies;
  929.     }

  930.     /**
  931.      *
  932.      * The utility method to check whether a host name is in a cookie's domain
  933.      * or not. Similar to {@link HttpCookie#domainMatches(String, String)} but
  934.      * implements domain matching rules according to
  935.      * <a href="https://tools.ietf.org/html/rfc6265#section-5.1.3">RFC 6265,
  936.      * section 5.1.3</a> instead of the rules from
  937.      * <a href="https://tools.ietf.org/html/rfc2965#section-3.3">RFC 2965,
  938.      * section 3.3.1</a>.
  939.      * <p>
  940.      * The former rules are also used by libcurl internally.
  941.      * <p>
  942.      * The rules are as follows
  943.      *
  944.      * A string matches another domain string if at least one of the following
  945.      * conditions holds:
  946.      * <ul>
  947.      * <li>The domain string and the string are identical. (Note that both the
  948.      * domain string and the string will have been canonicalized to lower case
  949.      * at this point.)</li>
  950.      * <li>All of the following conditions hold
  951.      * <ul>
  952.      * <li>The domain string is a suffix of the string.</li>
  953.      * <li>The last character of the string that is not included in the domain
  954.      * string is a %x2E (".") character.</li>
  955.      * <li>The string is a host name (i.e., not an IP address).</li>
  956.      * </ul>
  957.      * </li>
  958.      * </ul>
  959.      *
  960.      * @param host
  961.      *            the host to compare against the cookieDomain
  962.      * @param cookieDomain
  963.      *            the domain to compare against
  964.      * @return {@code true} if they domain-match; {@code false} if not
  965.      *
  966.      * @see <a href= "https://tools.ietf.org/html/rfc6265#section-5.1.3">RFC
  967.      *      6265, section 5.1.3 (Domain Matching)</a>
  968.      * @see <a href=
  969.      *      "https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8206092">JDK-8206092
  970.      *      : HttpCookie.domainMatches() does not match to sub-sub-domain</a>
  971.      */
  972.     static boolean matchesCookieDomain(String host, String cookieDomain) {
  973.         cookieDomain = cookieDomain.toLowerCase(Locale.ROOT);
  974.         host = host.toLowerCase(Locale.ROOT);
  975.         if (host.equals(cookieDomain)) {
  976.             return true;
  977.         } else {
  978.             if (!host.endsWith(cookieDomain)) {
  979.                 return false;
  980.             }
  981.             return host
  982.                     .charAt(host.length() - cookieDomain.length() - 1) == '.';
  983.         }
  984.     }

  985.     /**
  986.      * The utility method to check whether a path is matching a cookie path
  987.      * domain or not. The rules are defined by
  988.      * <a href="https://tools.ietf.org/html/rfc6265#section-5.1.4">RFC 6265,
  989.      * section 5.1.4</a>:
  990.      *
  991.      * A request-path path-matches a given cookie-path if at least one of the
  992.      * following conditions holds:
  993.      * <ul>
  994.      * <li>The cookie-path and the request-path are identical.</li>
  995.      * <li>The cookie-path is a prefix of the request-path, and the last
  996.      * character of the cookie-path is %x2F ("/").</li>
  997.      * <li>The cookie-path is a prefix of the request-path, and the first
  998.      * character of the request-path that is not included in the cookie- path is
  999.      * a %x2F ("/") character.</li>
  1000.      * </ul>
  1001.      * @param path
  1002.      *            the path to check
  1003.      * @param cookiePath
  1004.      *            the cookie's path
  1005.      *
  1006.      * @return {@code true} if they path-match; {@code false} if not
  1007.      */
  1008.     static boolean matchesCookiePath(String path, String cookiePath) {
  1009.         if (cookiePath.equals(path)) {
  1010.             return true;
  1011.         }
  1012.         if (!cookiePath.endsWith("/")) { //$NON-NLS-1$
  1013.             cookiePath += "/"; //$NON-NLS-1$
  1014.         }
  1015.         return path.startsWith(cookiePath);
  1016.     }

  1017.     private boolean isSmartHttp(HttpConnection c, String service) {
  1018.         final String expType = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
  1019.         final String actType = c.getContentType();
  1020.         return expType.equals(actType);
  1021.     }

  1022.     private boolean isGzipContent(HttpConnection c) {
  1023.         return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING))
  1024.                 || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING));
  1025.     }

  1026.     private void readSmartHeaders(InputStream in, String service)
  1027.             throws IOException {
  1028.         // A smart reply will have a '#' after the first 4 bytes, but
  1029.         // a dumb reply cannot contain a '#' until after byte 41. Do a
  1030.         // quick check to make sure its a smart reply before we parse
  1031.         // as a pkt-line stream.
  1032.         //
  1033.         final byte[] magic = new byte[5];
  1034.         IO.readFully(in, magic, 0, magic.length);
  1035.         if (magic[4] != '#') {
  1036.             throw new TransportException(uri, MessageFormat.format(
  1037.                     JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic)));
  1038.         }

  1039.         final PacketLineIn pckIn = new PacketLineIn(new UnionInputStream(
  1040.                 new ByteArrayInputStream(magic), in));
  1041.         final String exp = "# service=" + service; //$NON-NLS-1$
  1042.         final String act = pckIn.readString();
  1043.         if (!exp.equals(act)) {
  1044.             throw new TransportException(uri, MessageFormat.format(
  1045.                     JGitText.get().expectedGot, exp, act));
  1046.         }

  1047.         while (!PacketLineIn.isEnd(pckIn.readString())) {
  1048.             // for now, ignore the remaining header lines
  1049.         }
  1050.     }

  1051.     class HttpObjectDB extends WalkRemoteObjectDatabase {
  1052.         private final URL httpObjectsUrl;

  1053.         HttpObjectDB(URL b) {
  1054.             httpObjectsUrl = b;
  1055.         }

  1056.         @Override
  1057.         URIish getURI() {
  1058.             return new URIish(httpObjectsUrl);
  1059.         }

  1060.         @Override
  1061.         Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
  1062.             try {
  1063.                 return readAlternates(INFO_HTTP_ALTERNATES);
  1064.             } catch (FileNotFoundException err) {
  1065.                 // Fall through.
  1066.             }

  1067.             try {
  1068.                 return readAlternates(INFO_ALTERNATES);
  1069.             } catch (FileNotFoundException err) {
  1070.                 // Fall through.
  1071.             }

  1072.             return null;
  1073.         }

  1074.         @Override
  1075.         WalkRemoteObjectDatabase openAlternate(String location)
  1076.                 throws IOException {
  1077.             return new HttpObjectDB(new URL(httpObjectsUrl, location));
  1078.         }

  1079.         @Override
  1080.         BufferedReader openReader(String path) throws IOException {
  1081.             // Line oriented readable content is likely to compress well.
  1082.             // Request gzip encoding.
  1083.             InputStream is = open(path, AcceptEncoding.GZIP).in;
  1084.             return new BufferedReader(new InputStreamReader(is, UTF_8));
  1085.         }

  1086.         @Override
  1087.         Collection<String> getPackNames() throws IOException {
  1088.             final Collection<String> packs = new ArrayList<>();
  1089.             try (BufferedReader br = openReader(INFO_PACKS)) {
  1090.                 for (;;) {
  1091.                     final String s = br.readLine();
  1092.                     if (s == null || s.length() == 0)
  1093.                         break;
  1094.                     if (!s.startsWith("P pack-") || !s.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$
  1095.                         throw invalidAdvertisement(s);
  1096.                     packs.add(s.substring(2));
  1097.                 }
  1098.                 return packs;
  1099.             } catch (FileNotFoundException err) {
  1100.                 return packs;
  1101.             }
  1102.         }

  1103.         @Override
  1104.         FileStream open(String path) throws IOException {
  1105.             return open(path, AcceptEncoding.UNSPECIFIED);
  1106.         }

  1107.         FileStream open(String path, AcceptEncoding acceptEncoding)
  1108.                 throws IOException {
  1109.             final URL base = httpObjectsUrl;
  1110.             final URL u = new URL(base, path);
  1111.             final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding);
  1112.             switch (HttpSupport.response(c)) {
  1113.             case HttpConnection.HTTP_OK:
  1114.                 final InputStream in = openInputStream(c);
  1115.                 // If content is being gzipped and then transferred, the content
  1116.                 // length in the header is the zipped content length, not the
  1117.                 // actual content length.
  1118.                 if (!isGzipContent(c)) {
  1119.                     final int len = c.getContentLength();
  1120.                     return new FileStream(in, len);
  1121.                 }
  1122.                 return new FileStream(in);
  1123.             case HttpConnection.HTTP_NOT_FOUND:
  1124.                 throw new FileNotFoundException(u.toString());
  1125.             default:
  1126.                 throw new IOException(u.toString() + ": " //$NON-NLS-1$
  1127.                         + HttpSupport.response(c) + " " //$NON-NLS-1$
  1128.                         + c.getResponseMessage());
  1129.             }
  1130.         }

  1131.         Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
  1132.                 throws IOException, PackProtocolException {
  1133.             final TreeMap<String, Ref> avail = new TreeMap<>();
  1134.             for (;;) {
  1135.                 String line = br.readLine();
  1136.                 if (line == null)
  1137.                     break;

  1138.                 final int tab = line.indexOf('\t');
  1139.                 if (tab < 0)
  1140.                     throw invalidAdvertisement(line);

  1141.                 String name;
  1142.                 final ObjectId id;

  1143.                 name = line.substring(tab + 1);
  1144.                 id = ObjectId.fromString(line.substring(0, tab));
  1145.                 if (name.endsWith("^{}")) { //$NON-NLS-1$
  1146.                     name = name.substring(0, name.length() - 3);
  1147.                     final Ref prior = avail.get(name);
  1148.                     if (prior == null)
  1149.                         throw outOfOrderAdvertisement(name);

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

  1152.                     avail.put(name, new ObjectIdRef.PeeledTag(
  1153.                             Ref.Storage.NETWORK, name,
  1154.                             prior.getObjectId(), id));
  1155.                 } else {
  1156.                     Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
  1157.                             Ref.Storage.NETWORK, name, id));
  1158.                     if (prior != null)
  1159.                         throw duplicateAdvertisement(name);
  1160.                 }
  1161.             }
  1162.             return avail;
  1163.         }

  1164.         private PackProtocolException outOfOrderAdvertisement(String n) {
  1165.             return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n));
  1166.         }

  1167.         private PackProtocolException invalidAdvertisement(String n) {
  1168.             return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n));
  1169.         }

  1170.         private PackProtocolException duplicateAdvertisement(String n) {
  1171.             return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n));
  1172.         }

  1173.         @Override
  1174.         void close() {
  1175.             // We do not maintain persistent connections.
  1176.         }
  1177.     }

  1178.     class SmartHttpFetchConnection extends BasePackFetchConnection {
  1179.         private MultiRequestService svc;

  1180.         SmartHttpFetchConnection(InputStream advertisement)
  1181.                 throws TransportException {
  1182.             super(TransportHttp.this);
  1183.             statelessRPC = true;

  1184.             init(advertisement, DisabledOutputStream.INSTANCE);
  1185.             outNeedsEnd = false;
  1186.             readAdvertisedRefs();
  1187.         }

  1188.         @Override
  1189.         protected void doFetch(final ProgressMonitor monitor,
  1190.                 final Collection<Ref> want, final Set<ObjectId> have,
  1191.                 final OutputStream outputStream) throws TransportException {
  1192.             try {
  1193.                 svc = new MultiRequestService(SVC_UPLOAD_PACK);
  1194.                 init(svc.getInputStream(), svc.getOutputStream());
  1195.                 super.doFetch(monitor, want, have, outputStream);
  1196.             } finally {
  1197.                 svc = null;
  1198.             }
  1199.         }

  1200.         @Override
  1201.         protected void onReceivePack() {
  1202.             svc.finalRequest = true;
  1203.         }
  1204.     }

  1205.     class SmartHttpPushConnection extends BasePackPushConnection {
  1206.         SmartHttpPushConnection(InputStream advertisement)
  1207.                 throws TransportException {
  1208.             super(TransportHttp.this);
  1209.             statelessRPC = true;

  1210.             init(advertisement, DisabledOutputStream.INSTANCE);
  1211.             outNeedsEnd = false;
  1212.             readAdvertisedRefs();
  1213.         }

  1214.         @Override
  1215.         protected void doPush(final ProgressMonitor monitor,
  1216.                 final Map<String, RemoteRefUpdate> refUpdates,
  1217.                 OutputStream outputStream) throws TransportException {
  1218.             final Service svc = new MultiRequestService(SVC_RECEIVE_PACK);
  1219.             init(svc.getInputStream(), svc.getOutputStream());
  1220.             super.doPush(monitor, refUpdates, outputStream);
  1221.         }
  1222.     }

  1223.     /** Basic service for sending and receiving HTTP requests. */
  1224.     abstract class Service {
  1225.         protected final String serviceName;

  1226.         protected final String requestType;

  1227.         protected final String responseType;

  1228.         protected HttpConnection conn;

  1229.         protected HttpOutputStream out;

  1230.         protected final HttpExecuteStream execute;

  1231.         final UnionInputStream in;

  1232.         Service(String serviceName) {
  1233.             this.serviceName = serviceName;
  1234.             this.requestType = "application/x-" + serviceName + "-request"; //$NON-NLS-1$ //$NON-NLS-2$
  1235.             this.responseType = "application/x-" + serviceName + "-result"; //$NON-NLS-1$ //$NON-NLS-2$

  1236.             this.out = new HttpOutputStream();
  1237.             this.execute = new HttpExecuteStream();
  1238.             this.in = new UnionInputStream(execute);
  1239.         }

  1240.         void openStream() throws IOException {
  1241.             conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
  1242.                     AcceptEncoding.GZIP);
  1243.             conn.setInstanceFollowRedirects(false);
  1244.             conn.setDoOutput(true);
  1245.             conn.setRequestProperty(HDR_CONTENT_TYPE, requestType);
  1246.             conn.setRequestProperty(HDR_ACCEPT, responseType);
  1247.         }

  1248.         void sendRequest() throws IOException {
  1249.             // Try to compress the content, but only if that is smaller.
  1250.             TemporaryBuffer buf = new TemporaryBuffer.Heap(
  1251.                     http.getPostBuffer());
  1252.             try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) {
  1253.                 out.writeTo(gzip, null);
  1254.                 if (out.length() < buf.length())
  1255.                     buf = out;
  1256.             } catch (IOException err) {
  1257.                 // Most likely caused by overflowing the buffer, meaning
  1258.                 // its larger if it were compressed. Don't compress.
  1259.                 buf = out;
  1260.             }

  1261.             HttpAuthMethod authenticator = null;
  1262.             Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
  1263.             // Counts number of repeated authentication attempts using the same
  1264.             // authentication scheme
  1265.             int authAttempts = 1;
  1266.             int redirects = 0;
  1267.             for (;;) {
  1268.                 try {
  1269.                     // The very first time we will try with the authentication
  1270.                     // method used on the initial GET request. This is a hint
  1271.                     // only; it may fail. If so, we'll then re-try with proper
  1272.                     // 401 handling, going through the available authentication
  1273.                     // schemes.
  1274.                     openStream();
  1275.                     if (buf != out) {
  1276.                         conn.setRequestProperty(HDR_CONTENT_ENCODING,
  1277.                                 ENCODING_GZIP);
  1278.                     }
  1279.                     conn.setFixedLengthStreamingMode((int) buf.length());
  1280.                     try (OutputStream httpOut = conn.getOutputStream()) {
  1281.                         buf.writeTo(httpOut, null);
  1282.                     }

  1283.                     final int status = HttpSupport.response(conn);
  1284.                     switch (status) {
  1285.                     case HttpConnection.HTTP_OK:
  1286.                         // We're done.
  1287.                         return;

  1288.                     case HttpConnection.HTTP_NOT_FOUND:
  1289.                         throw createNotFoundException(uri, conn.getURL(),
  1290.                                 conn.getResponseMessage());

  1291.                     case HttpConnection.HTTP_FORBIDDEN:
  1292.                         throw new TransportException(uri,
  1293.                                 MessageFormat.format(
  1294.                                         JGitText.get().serviceNotPermitted,
  1295.                                         baseUrl, serviceName));

  1296.                     case HttpConnection.HTTP_MOVED_PERM:
  1297.                     case HttpConnection.HTTP_MOVED_TEMP:
  1298.                     case HttpConnection.HTTP_11_MOVED_TEMP:
  1299.                         // SEE_OTHER after a POST doesn't make sense for a git
  1300.                         // server, so we don't handle it here and thus we'll
  1301.                         // report an error in openResponse() later on.
  1302.                         if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
  1303.                             // Let openResponse() issue an error
  1304.                             return;
  1305.                         }
  1306.                         currentUri = redirect(conn.getHeaderField(HDR_LOCATION),
  1307.                                 '/' + serviceName, redirects++);
  1308.                         try {
  1309.                             baseUrl = toURL(currentUri);
  1310.                         } catch (MalformedURLException e) {
  1311.                             throw new TransportException(uri,
  1312.                                     MessageFormat.format(
  1313.                                             JGitText.get().invalidRedirectLocation,
  1314.                                             baseUrl, currentUri),
  1315.                                     e);
  1316.                         }
  1317.                         continue;

  1318.                     case HttpConnection.HTTP_UNAUTHORIZED:
  1319.                         HttpAuthMethod nextMethod = HttpAuthMethod
  1320.                                 .scanResponse(conn, ignoreTypes);
  1321.                         switch (nextMethod.getType()) {
  1322.                         case NONE:
  1323.                             throw new TransportException(uri,
  1324.                                     MessageFormat.format(
  1325.                                             JGitText.get().authenticationNotSupported,
  1326.                                             conn.getURL()));
  1327.                         case NEGOTIATE:
  1328.                             // RFC 4559 states "When using the SPNEGO [...] with
  1329.                             // [...] POST, the authentication should be complete
  1330.                             // [...] before sending the user data." So in theory
  1331.                             // the initial GET should have been authenticated
  1332.                             // already. (Unless there was a redirect?)
  1333.                             //
  1334.                             // We try this only once:
  1335.                             ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
  1336.                             if (authenticator != null) {
  1337.                                 ignoreTypes.add(authenticator.getType());
  1338.                             }
  1339.                             authAttempts = 1;
  1340.                             // We only do the Kerberos part of SPNEGO, which
  1341.                             // requires only one round.
  1342.                             break;
  1343.                         default:
  1344.                             // DIGEST or BASIC. Let's be sure we ignore
  1345.                             // NEGOTIATE; if it was available, we have tried it
  1346.                             // before.
  1347.                             ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
  1348.                             if (authenticator == null || authenticator
  1349.                                     .getType() != nextMethod.getType()) {
  1350.                                 if (authenticator != null) {
  1351.                                     ignoreTypes.add(authenticator.getType());
  1352.                                 }
  1353.                                 authAttempts = 1;
  1354.                             }
  1355.                             break;
  1356.                         }
  1357.                         authMethod = nextMethod;
  1358.                         authenticator = nextMethod;
  1359.                         CredentialsProvider credentialsProvider = getCredentialsProvider();
  1360.                         if (credentialsProvider == null) {
  1361.                             throw new TransportException(uri,
  1362.                                     JGitText.get().noCredentialsProvider);
  1363.                         }
  1364.                         if (authAttempts > 1) {
  1365.                             credentialsProvider.reset(currentUri);
  1366.                         }
  1367.                         if (3 < authAttempts || !authMethod
  1368.                                 .authorize(currentUri, credentialsProvider)) {
  1369.                             throw new TransportException(uri,
  1370.                                     JGitText.get().notAuthorized);
  1371.                         }
  1372.                         authAttempts++;
  1373.                         continue;

  1374.                     default:
  1375.                         // Just return here; openResponse() will report an
  1376.                         // appropriate error.
  1377.                         return;
  1378.                     }
  1379.                 } catch (SSLHandshakeException e) {
  1380.                     handleSslFailure(e);
  1381.                     continue; // Re-try
  1382.                 } catch (IOException e) {
  1383.                     if (authenticator == null || authMethod
  1384.                             .getType() != HttpAuthMethod.Type.NONE) {
  1385.                         // Can happen for instance if the server advertises
  1386.                         // Negotiate, but the client isn't configured for
  1387.                         // Kerberos. The first time (authenticator == null) we
  1388.                         // must re-try even if the authMethod was NONE: this may
  1389.                         // occur if the server advertised NTLM on the GET
  1390.                         // and the HttpConnection managed to successfully
  1391.                         // authenticate under the hood with NTLM. We might not
  1392.                         // have picked this up on the GET's 200 response.
  1393.                         if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
  1394.                             ignoreTypes.add(authMethod.getType());
  1395.                         }
  1396.                         // Start over with the remaining available methods.
  1397.                         authMethod = HttpAuthMethod.Type.NONE.method(null);
  1398.                         authenticator = authMethod;
  1399.                         authAttempts = 1;
  1400.                         continue;
  1401.                     }
  1402.                     throw e;
  1403.                 }
  1404.             }
  1405.         }

  1406.         void openResponse() throws IOException {
  1407.             final int status = HttpSupport.response(conn);
  1408.             if (status != HttpConnection.HTTP_OK) {
  1409.                 throw new TransportException(uri, status + " " //$NON-NLS-1$
  1410.                         + conn.getResponseMessage());
  1411.             }

  1412.             final String contentType = conn.getContentType();
  1413.             if (!responseType.equals(contentType)) {
  1414.                 conn.getInputStream().close();
  1415.                 throw wrongContentType(responseType, contentType);
  1416.             }
  1417.         }

  1418.         HttpOutputStream getOutputStream() {
  1419.             return out;
  1420.         }

  1421.         InputStream getInputStream() {
  1422.             return in;
  1423.         }

  1424.         abstract void execute() throws IOException;

  1425.         class HttpExecuteStream extends InputStream {
  1426.             @Override
  1427.             public int read() throws IOException {
  1428.                 execute();
  1429.                 return -1;
  1430.             }

  1431.             @Override
  1432.             public int read(byte[] b, int off, int len) throws IOException {
  1433.                 execute();
  1434.                 return -1;
  1435.             }

  1436.             @Override
  1437.             public long skip(long n) throws IOException {
  1438.                 execute();
  1439.                 return 0;
  1440.             }
  1441.         }

  1442.         class HttpOutputStream extends TemporaryBuffer {
  1443.             HttpOutputStream() {
  1444.                 super(http.getPostBuffer());
  1445.             }

  1446.             @Override
  1447.             protected OutputStream overflow() throws IOException {
  1448.                 openStream();
  1449.                 conn.setChunkedStreamingMode(0);
  1450.                 return conn.getOutputStream();
  1451.             }
  1452.         }
  1453.     }

  1454.     /**
  1455.      * State required to speak multiple HTTP requests with the remote.
  1456.      * <p>
  1457.      * A service wrapper provides a normal looking InputStream and OutputStream
  1458.      * pair which are connected via HTTP to the named remote service. Writing to
  1459.      * the OutputStream is buffered until either the buffer overflows, or
  1460.      * reading from the InputStream occurs. If overflow occurs HTTP/1.1 and its
  1461.      * chunked transfer encoding is used to stream the request data to the
  1462.      * remote service. If the entire request fits in the memory buffer, the
  1463.      * older HTTP/1.0 standard and a fixed content length is used instead.
  1464.      * <p>
  1465.      * It is an error to attempt to read without there being outstanding data
  1466.      * ready for transmission on the OutputStream.
  1467.      * <p>
  1468.      * No state is preserved between write-read request pairs. The caller is
  1469.      * responsible for replaying state vector information as part of the request
  1470.      * data written to the OutputStream. Any session HTTP cookies may or may not
  1471.      * be preserved between requests, it is left up to the JVM's implementation
  1472.      * of the HTTP client.
  1473.      */
  1474.     class MultiRequestService extends Service {
  1475.         boolean finalRequest;

  1476.         MultiRequestService(String serviceName) {
  1477.             super(serviceName);
  1478.         }

  1479.         /** Keep opening send-receive pairs to the given URI. */
  1480.         @Override
  1481.         void execute() throws IOException {
  1482.             out.close();

  1483.             if (conn == null) {
  1484.                 if (out.length() == 0) {
  1485.                     // Request output hasn't started yet, but more data is being
  1486.                     // requested. If there is no request data buffered and the
  1487.                     // final request was already sent, do nothing to ensure the
  1488.                     // caller is shown EOF on the InputStream; otherwise an
  1489.                     // programming error has occurred within this module.
  1490.                     if (finalRequest)
  1491.                         return;
  1492.                     throw new TransportException(uri,
  1493.                             JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported);
  1494.                 }

  1495.                 sendRequest();
  1496.             }

  1497.             out.reset();

  1498.             openResponse();

  1499.             in.add(openInputStream(conn));
  1500.             if (!finalRequest)
  1501.                 in.add(execute);
  1502.             conn = null;
  1503.         }
  1504.     }

  1505.     /** Service for maintaining a single long-poll connection. */
  1506.     class LongPollService extends Service {
  1507.         /**
  1508.          * @param serviceName
  1509.          */
  1510.         LongPollService(String serviceName) {
  1511.             super(serviceName);
  1512.         }

  1513.         /** Only open one send-receive request. */
  1514.         @Override
  1515.         void execute() throws IOException {
  1516.             out.close();
  1517.             if (conn == null)
  1518.                 sendRequest();
  1519.             openResponse();
  1520.             in.add(openInputStream(conn));
  1521.         }
  1522.     }
  1523. }