diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 3f21ab01c..545318424 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -38,6 +38,7 @@ Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", org.eclipse.jgit.storage.pack;version="[3.3.0,3.4.0)", org.eclipse.jgit.submodule;version="[3.3.0,3.4.0)", org.eclipse.jgit.transport;version="[3.3.0,3.4.0)", + org.eclipse.jgit.transport.http;version="[3.3.0,3.4.0)", org.eclipse.jgit.treewalk;version="[3.3.0,3.4.0)", org.eclipse.jgit.treewalk.filter;version="[3.3.0,3.4.0)", org.eclipse.jgit.util;version="[3.3.0,3.4.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java index c142bc23a..5a64b458f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java @@ -45,7 +45,6 @@ import static org.junit.Assert.fail; import java.io.IOException; -import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; @@ -53,6 +52,7 @@ import java.util.List; import java.util.Map; +import org.eclipse.jgit.transport.http.JDKHttpConnection; import org.junit.Test; public class HttpAuthTest { @@ -71,7 +71,7 @@ public class HttpAuthTest { private static String DIGEST = "Digest"; @Test - public void testHttpAuthScanResponse() throws MalformedURLException { + public void testHttpAuthScanResponse() { checkResponse(new String[] { basicHeader }, BASIC); checkResponse(new String[] { digestHeader }, DIGEST); checkResponse(new String[] { basicHeader, digestHeader }, DIGEST); @@ -83,10 +83,15 @@ public void testHttpAuthScanResponse() throws MalformedURLException { } private static void checkResponse(String[] headers, - String expectedAuthMethod) throws MalformedURLException { + String expectedAuthMethod) { - AuthHeadersResponse responce = new AuthHeadersResponse(headers); - HttpAuthMethod authMethod = HttpAuthMethod.scanResponse(responce); + AuthHeadersResponse response = null; + try { + response = new AuthHeadersResponse(headers); + } catch (IOException e) { + fail("Couldn't instantiate AuthHeadersResponse: " + e.toString()); + } + HttpAuthMethod authMethod = HttpAuthMethod.scanResponse(response); if (!expectedAuthMethod.equals(getAuthMethodName(authMethod))) { fail("Wrong authentication method: expected " + expectedAuthMethod @@ -98,20 +103,15 @@ private static String getAuthMethodName(HttpAuthMethod authMethod) { return authMethod.getClass().getSimpleName(); } - private static class AuthHeadersResponse extends HttpURLConnection { + private static class AuthHeadersResponse extends JDKHttpConnection { Map> headerFields = new HashMap>(); public AuthHeadersResponse(String[] authHeaders) - throws MalformedURLException { + throws MalformedURLException, IOException { super(new URL(URL_SAMPLE)); parseHeaders(authHeaders); } - @Override - public void disconnect() { - fail("The disconnect method shouldn't be invoked"); - } - @Override public boolean usingProxy() { return false; diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters new file mode 100644 index 000000000..87a931c87 --- /dev/null +++ b/org.eclipse.jgit/.settings/.api_filters @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 05a94c569..2b0598051 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -104,8 +104,11 @@ Export-Package: org.eclipse.jgit.api;version="3.3.0"; org.eclipse.jgit.util.io, org.eclipse.jgit.internal.storage.file, org.eclipse.jgit.lib, + org.eclipse.jgit.transport.http, org.eclipse.jgit.errors, org.eclipse.jgit.storage.pack", + org.eclipse.jgit.transport.http;version="3.3.0"; + uses:="javax.net.ssl", org.eclipse.jgit.transport.resolver;version="3.3.0"; uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport", org.eclipse.jgit.treewalk;version="3.3.0"; @@ -117,7 +120,7 @@ Export-Package: org.eclipse.jgit.api;version="3.3.0"; org.eclipse.jgit.treewalk.filter;version="3.3.0"; uses:="org.eclipse.jgit.treewalk", org.eclipse.jgit.util;version="3.3.0"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.storage.file", + uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport.http,org.eclipse.jgit.storage.file", org.eclipse.jgit.util.io;version="3.3.0" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java index 8acba21bd..225af4b49 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java @@ -48,7 +48,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -60,6 +59,7 @@ import java.util.Map.Entry; import java.util.Random; +import org.eclipse.jgit.transport.http.HttpConnection; import org.eclipse.jgit.util.Base64; /** @@ -81,7 +81,7 @@ abstract class HttpAuthMethod { * the connection that failed. * @return new authentication method to try. */ - static HttpAuthMethod scanResponse(final HttpURLConnection conn) { + static HttpAuthMethod scanResponse(final HttpConnection conn) { final Map> headers = conn.getHeaderFields(); HttpAuthMethod authentication = NONE; @@ -168,7 +168,7 @@ boolean authorize(URIish uri, CredentialsProvider credentialsProvider) { * @param conn * @throws IOException */ - abstract void configureRequest(HttpURLConnection conn) throws IOException; + abstract void configureRequest(HttpConnection conn) throws IOException; /** Performs no user authentication. */ private static class None extends HttpAuthMethod { @@ -178,7 +178,7 @@ void authorize(String user, String pass) { } @Override - void configureRequest(HttpURLConnection conn) throws IOException { + void configureRequest(HttpConnection conn) throws IOException { // Do nothing when no authentication is enabled. } } @@ -198,7 +198,7 @@ void authorize(final String username, final String password) { } @Override - void configureRequest(final HttpURLConnection conn) throws IOException { + void configureRequest(final HttpConnection conn) throws IOException { String ident = user + ":" + pass; //$NON-NLS-1$ String enc = Base64.encodeBytes(ident.getBytes("UTF-8")); //$NON-NLS-1$ conn.setRequestProperty(HDR_AUTHORIZATION, NAME + " " + enc); //$NON-NLS-1$ @@ -238,7 +238,7 @@ void authorize(final String username, final String password) { @SuppressWarnings("boxing") @Override - void configureRequest(final HttpURLConnection conn) throws IOException { + void configureRequest(final HttpConnection conn) throws IOException { final Map r = new LinkedHashMap(); final String realm = params.get("realm"); //$NON-NLS-1$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java index 4f8f89df9..006e84632 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java @@ -46,12 +46,40 @@ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.http.HttpConnectionFactory; +import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; /** * The base class for transports that use HTTP as underlying protocol. This class * allows customizing HTTP connection settings. */ public abstract class HttpTransport extends Transport { + /** + * factory for creating HTTP connections + * + * @since 3.3 + */ + protected static HttpConnectionFactory connectionFactory = new JDKHttpConnectionFactory(); + + /** + * @return the {@link HttpConnectionFactory} used to create new connections + * @since 3.3 + */ + public static HttpConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + /** + * Set the {@link HttpConnectionFactory} to be used to create new + * connections + * + * @param cf + * @since 3.3 + */ + public static void setConnectionFactory(HttpConnectionFactory cf) { + connectionFactory = cf; + } + /** * Create a new transport instance. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index 75838466f..fe55e2377 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -62,12 +62,10 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.Proxy; import java.net.ProxySelector; import java.net.URL; -import java.net.URLConnection; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; @@ -85,8 +83,6 @@ import java.util.zip.GZIPOutputStream; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -106,6 +102,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.transport.http.HttpConnection; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; @@ -131,6 +128,7 @@ */ public class TransportHttp extends HttpTransport implements WalkTransport, PackTransport { + private static final String SVC_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$ private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$ @@ -305,7 +303,7 @@ public FetchConnection openFetch() throws TransportException, NotSupportedException { final String service = SVC_UPLOAD_PACK; try { - final HttpURLConnection c = connect(service); + final HttpConnection c = connect(service); final InputStream in = openInputStream(c); try { if (isSmartHttp(c, service)) { @@ -346,10 +344,10 @@ private FetchConnection newDumbConnection(InputStream in) // is not there) download HEAD by itself as a loose file and do // the resolution by hand. // - HttpURLConnection conn = httpOpen(new URL(baseUrl, Constants.HEAD)); + HttpConnection conn = httpOpen(new URL(baseUrl, Constants.HEAD)); int status = HttpSupport.response(conn); switch (status) { - case HttpURLConnection.HTTP_OK: { + case HttpConnection.HTTP_OK: { br = toBufferedReader(openInputStream(conn)); try { String line = br.readLine(); @@ -371,7 +369,7 @@ private FetchConnection newDumbConnection(InputStream in) break; } - case HttpURLConnection.HTTP_NOT_FOUND: + case HttpConnection.HTTP_NOT_FOUND: break; default: @@ -395,7 +393,7 @@ public PushConnection openPush() throws NotSupportedException, TransportException { final String service = SVC_RECEIVE_PACK; try { - final HttpURLConnection c = connect(service); + final HttpConnection c = connect(service); final InputStream in = openInputStream(c); try { if (isSmartHttp(c, service)) { @@ -427,7 +425,7 @@ public void close() { // No explicit connections are maintained. } - private HttpURLConnection connect(final String service) + private HttpConnection connect(final String service) throws TransportException, NotSupportedException { final URL u; try { @@ -452,7 +450,7 @@ private HttpURLConnection connect(final String service) try { int authAttempts = 1; for (;;) { - final HttpURLConnection conn = httpOpen(u); + final HttpConnection conn = httpOpen(u); if (useSmartHttp) { String exp = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$ conn.setRequestProperty(HDR_ACCEPT, exp + ", */*"); //$NON-NLS-1$ @@ -461,14 +459,14 @@ private HttpURLConnection connect(final String service) } final int status = HttpSupport.response(conn); switch (status) { - case HttpURLConnection.HTTP_OK: + case HttpConnection.HTTP_OK: return conn; - case HttpURLConnection.HTTP_NOT_FOUND: + case HttpConnection.HTTP_NOT_FOUND: throw new NoRemoteRepositoryException(uri, MessageFormat.format(JGitText.get().uriNotFound, u)); - case HttpURLConnection.HTTP_UNAUTHORIZED: + case HttpConnection.HTTP_UNAUTHORIZED: authMethod = HttpAuthMethod.scanResponse(conn); if (authMethod == HttpAuthMethod.NONE) throw new TransportException(uri, MessageFormat.format( @@ -482,7 +480,7 @@ private HttpURLConnection connect(final String service) authAttempts++; continue; - case HttpURLConnection.HTTP_FORBIDDEN: + case HttpConnection.HTTP_FORBIDDEN: throw new TransportException(uri, MessageFormat.format( JGitText.get().serviceNotPermitted, service)); @@ -500,23 +498,23 @@ private HttpURLConnection connect(final String service) } } - final HttpURLConnection httpOpen(URL u) throws IOException { + final HttpConnection httpOpen(URL u) throws IOException { return httpOpen(METHOD_GET, u); } /** * Open an HTTP connection. - * + * * @param method * @param u * @return the connection * @throws IOException - * @since 3.2 + * @since 3.3 */ - protected HttpURLConnection httpOpen(String method, URL u) + protected HttpConnection httpOpen(String method, URL u) throws IOException { final Proxy proxy = HttpSupport.proxyFor(proxySelector, u); - HttpURLConnection conn = (HttpURLConnection) u.openConnection(proxy); + HttpConnection conn = connectionFactory.create(u, proxy); if (!http.sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$ disableSslVerify(conn); @@ -537,15 +535,12 @@ protected HttpURLConnection httpOpen(String method, URL u) return conn; } - private void disableSslVerify(URLConnection conn) + private void disableSslVerify(HttpConnection conn) throws IOException { final TrustManager[] trustAllCerts = new TrustManager[] { new DummyX509TrustManager() }; try { - SSLContext ctx = SSLContext.getInstance("SSL"); //$NON-NLS-1$ - ctx.init(null, trustAllCerts, null); - final HttpsURLConnection sslConn = (HttpsURLConnection) conn; - sslConn.setSSLSocketFactory(ctx.getSocketFactory()); - sslConn.setHostnameVerifier(new DummyHostnameVerifier()); + conn.configure(null, trustAllCerts, null); + conn.setHostnameVerifier(new DummyHostnameVerifier()); } catch (KeyManagementException e) { throw new IOException(e.getMessage()); } catch (NoSuchAlgorithmException e) { @@ -553,7 +548,7 @@ private void disableSslVerify(URLConnection conn) } } - final InputStream openInputStream(HttpURLConnection conn) + final InputStream openInputStream(HttpConnection conn) throws IOException { InputStream input = conn.getInputStream(); if (ENCODING_GZIP.equals(conn.getHeaderField(HDR_CONTENT_ENCODING))) @@ -566,7 +561,7 @@ IOException wrongContentType(String expType, String actType) { return new TransportException(uri, why); } - private boolean isSmartHttp(final HttpURLConnection c, final String service) { + private boolean isSmartHttp(final HttpConnection c, final String service) { final String expType = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$ final String actType = c.getContentType(); return expType.equals(actType); @@ -662,13 +657,13 @@ Collection getPackNames() throws IOException { FileStream open(final String path) throws IOException { final URL base = objectsUrl; final URL u = new URL(base, path); - final HttpURLConnection c = httpOpen(u); + final HttpConnection c = httpOpen(u); switch (HttpSupport.response(c)) { - case HttpURLConnection.HTTP_OK: + case HttpConnection.HTTP_OK: final InputStream in = openInputStream(c); final int len = c.getContentLength(); return new FileStream(in, len); - case HttpURLConnection.HTTP_NOT_FOUND: + case HttpConnection.HTTP_NOT_FOUND: throw new FileNotFoundException(u.toString()); default: throw new IOException(u.toString() + ": " //$NON-NLS-1$ @@ -794,7 +789,7 @@ abstract class Service { protected final String responseType; - protected HttpURLConnection conn; + protected HttpConnection conn; protected HttpOutputStream out; @@ -849,7 +844,7 @@ void sendRequest() throws IOException { void openResponse() throws IOException { final int status = HttpSupport.response(conn); - if (status != HttpURLConnection.HTTP_OK) { + if (status != HttpConnection.HTTP_OK) { throw new TransportException(uri, status + " " //$NON-NLS-1$ + conn.getResponseMessage()); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java new file mode 100644 index 000000000..0cf12aaa2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2013 Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * The interface of connections used during HTTP communication. This interface + * is that subset of the interface exposed by {@link HttpURLConnection} which is + * used by JGit + * + * @since 3.3 + */ +public interface HttpConnection { + /** + * @see HttpURLConnection#HTTP_OK + */ + public static final int HTTP_OK = java.net.HttpURLConnection.HTTP_OK; + + /** + * @see HttpURLConnection#HTTP_NOT_FOUND + */ + public static final int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND; + + /** + * @see HttpURLConnection#HTTP_UNAUTHORIZED + */ + public static final int HTTP_UNAUTHORIZED = java.net.HttpURLConnection.HTTP_UNAUTHORIZED; + + /** + * @see HttpURLConnection#HTTP_FORBIDDEN + */ + public static final int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN; + + /** + * @see HttpURLConnection#getResponseCode() + * @return the HTTP Status-Code, or -1 + * @throws IOException + */ + public int getResponseCode() throws IOException; + + /** + * @see HttpURLConnection#getURL() + * @return the URL. + */ + public URL getURL(); + + /** + * @see HttpURLConnection#getResponseMessage() + * @return the HTTP response message, or null + * @throws IOException + */ + public String getResponseMessage() throws IOException; + + /** + * @see HttpURLConnection#getHeaderFields() + * @return a Map of header fields + */ + public Map> getHeaderFields(); + + /** + * @see HttpURLConnection#setRequestProperty(String, String) + * @param key + * the keyword by which the request is known (e.g., " + * Accept"). + * @param value + * the value associated with it. + */ + public void setRequestProperty(String key, String value); + + /** + * @see HttpURLConnection#setRequestMethod(String) + * @param method + * the HTTP method + * @exception ProtocolException + * if the method cannot be reset or if the requested method + * isn't valid for HTTP. + */ + public void setRequestMethod(String method) + throws ProtocolException; + + /** + * @see HttpURLConnection#setUseCaches(boolean) + * @param usecaches + * a boolean indicating whether or not to allow + * caching + */ + public void setUseCaches(boolean usecaches); + + /** + * @see HttpURLConnection#setConnectTimeout(int) + * @param timeout + * an int that specifies the connect timeout value + * in milliseconds + */ + public void setConnectTimeout(int timeout); + + /** + * @see HttpURLConnection#setReadTimeout(int) + * @param timeout + * an int that specifies the timeout value to be + * used in milliseconds + */ + public void setReadTimeout(int timeout); + + /** + * @see HttpURLConnection#getContentType() + * @return the content type of the resource that the URL references, or + * null if not known. + */ + public String getContentType(); + + /** + * @see HttpURLConnection#getInputStream() + * @return an input stream that reads from this open connection. + * @exception IOException + * if an I/O error occurs while creating the input stream. + */ + public InputStream getInputStream() throws IOException; + + /** + * @see HttpURLConnection#getHeaderField(String) + * @param name + * the name of a header field. + * @return the value of the named header field, or null if + * there is no such field in the header. + */ + public String getHeaderField(String name); + + /** + * @see HttpURLConnection#getContentLength() + * @return the content length of the resource that this connection's URL + * references, {@code -1} if the content length is not known, or if + * the content length is greater than Integer.MAX_VALUE. + */ + public int getContentLength(); + + /** + * @see HttpURLConnection#setInstanceFollowRedirects(boolean) + * @param followRedirects + * a boolean indicating whether or not to follow + * HTTP redirects. + */ + public void setInstanceFollowRedirects(boolean followRedirects); + + /** + * @see HttpURLConnection#setDoOutput(boolean) + * @param dooutput the new value. + */ + public void setDoOutput(boolean dooutput); + + /** + * @see HttpURLConnection#setFixedLengthStreamingMode(int) + * @param contentLength + * The number of bytes which will be written to the OutputStream. + * + */ + public void setFixedLengthStreamingMode(int contentLength); + + /** + * @see HttpURLConnection#getOutputStream() + * @return an output stream that writes to this connection. + * @throws IOException + */ + public OutputStream getOutputStream() throws IOException; + + /** + * @see HttpURLConnection#setChunkedStreamingMode(int) + * @param chunklen + * The number of bytes to write in each chunk. If chunklen is + * less than or equal to zero, a default value will be used. + */ + public void setChunkedStreamingMode(int chunklen); + + /** + * @see HttpURLConnection#getRequestMethod() + * @return the HTTP request method + */ + public String getRequestMethod(); + + /** + * @see HttpURLConnection#usingProxy() + * @return a boolean indicating if the connection is using a proxy. + */ + public boolean usingProxy(); + + /** + * @see HttpURLConnection#connect() + * @throws IOException + */ + public void connect() throws IOException; + + /** + * Configure the connection so that it can be used for https communication. + * + * @param km + * the keymanager managing the key material used to authenticate + * the local SSLSocket to its peer + * @param tm + * the trustmanager responsible for managing the trust material + * that is used when making trust decisions, and for deciding + * whether credentials presented by a peer should be accepted. + * @param random + * the source of randomness for this generator or null. See + * {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} + * @throws NoSuchAlgorithmException + * @throws KeyManagementException + */ + public void configure(KeyManager[] km, TrustManager[] tm, + SecureRandom random) throws NoSuchAlgorithmException, + KeyManagementException; + + /** + * Set the {@link HostnameVerifier} used during https communication + * + * @param hostnameverifier + * @throws NoSuchAlgorithmException + * @throws KeyManagementException + */ + public void setHostnameVerifier(HostnameVerifier hostnameverifier) + throws NoSuchAlgorithmException, KeyManagementException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java new file mode 100644 index 000000000..591353c18 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013 Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport.http; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; + +/** + * The interface of a factory returning {@link HttpConnection} + * + * @since 3.3 + */ +public interface HttpConnectionFactory { + /** + * Creates a new connection to a destination defined by a {@link URL} + * + * @param url + * @return a {@link HttpConnection} + * @throws IOException + */ + public HttpConnection create(URL url) throws IOException; + + /** + * Creates a new connection to a destination defined by a {@link URL} using + * a proxy + * + * @param url + * @param proxy + * the proxy to be used + * @return a {@link HttpConnection} + * + * @throws IOException + */ + public HttpConnection create(URL url, Proxy proxy) + throws IOException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java new file mode 100644 index 000000000..bde5e1da2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2013 Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.Proxy; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * A {@link HttpConnection} which simply delegates every call to a + * {@link HttpURLConnection}. This is the default implementation used by JGit + * + * @since 3.3 + */ +public class JDKHttpConnection implements HttpConnection { + HttpURLConnection wrappedUrlConnection; + + /** + * @param url + * @throws MalformedURLException + * @throws IOException + */ + protected JDKHttpConnection(URL url) + throws MalformedURLException, + IOException { + this.wrappedUrlConnection = (HttpURLConnection) url.openConnection(); + } + + /** + * @param url + * @param proxy + * @throws MalformedURLException + * @throws IOException + */ + protected JDKHttpConnection(URL url, Proxy proxy) + throws MalformedURLException, IOException { + this.wrappedUrlConnection = (HttpURLConnection) url + .openConnection(proxy); + } + + public int getResponseCode() throws IOException { + return wrappedUrlConnection.getResponseCode(); + } + + public URL getURL() { + return wrappedUrlConnection.getURL(); + } + + public String getResponseMessage() throws IOException { + return wrappedUrlConnection.getResponseMessage(); + } + + public Map> getHeaderFields() { + return wrappedUrlConnection.getHeaderFields(); + } + + public void setRequestProperty(String key, String value) { + wrappedUrlConnection.setRequestProperty(key, value); + } + + public void setRequestMethod(String method) throws ProtocolException { + wrappedUrlConnection.setRequestMethod(method); + } + + public void setUseCaches(boolean usecaches) { + wrappedUrlConnection.setUseCaches(usecaches); + } + + public void setConnectTimeout(int timeout) { + wrappedUrlConnection.setConnectTimeout(timeout); + } + + public void setReadTimeout(int timeout) { + wrappedUrlConnection.setReadTimeout(timeout); + } + + public String getContentType() { + return wrappedUrlConnection.getContentType(); + } + + public InputStream getInputStream() throws IOException { + return wrappedUrlConnection.getInputStream(); + } + + public String getHeaderField(String name) { + return wrappedUrlConnection.getHeaderField(name); + } + + public int getContentLength() { + return wrappedUrlConnection.getContentLength(); + } + + public void setInstanceFollowRedirects(boolean followRedirects) { + wrappedUrlConnection.setInstanceFollowRedirects(followRedirects); + } + + public void setDoOutput(boolean dooutput) { + wrappedUrlConnection.setDoOutput(dooutput); + } + + public void setFixedLengthStreamingMode(int contentLength) { + wrappedUrlConnection.setFixedLengthStreamingMode(contentLength); + } + + public OutputStream getOutputStream() throws IOException { + return wrappedUrlConnection.getOutputStream(); + } + + public void setChunkedStreamingMode(int chunklen) { + wrappedUrlConnection.setChunkedStreamingMode(chunklen); + } + + public String getRequestMethod() { + return wrappedUrlConnection.getRequestMethod(); + } + + public boolean usingProxy() { + return wrappedUrlConnection.usingProxy(); + } + + public void connect() throws IOException { + wrappedUrlConnection.connect(); + } + + public void setHostnameVerifier(HostnameVerifier hostnameverifier) { + ((HttpsURLConnection) wrappedUrlConnection) + .setHostnameVerifier(hostnameverifier); + } + + public void configure(KeyManager[] km, TrustManager[] tm, + SecureRandom random) throws NoSuchAlgorithmException, + KeyManagementException { + SSLContext ctx = SSLContext.getInstance("SSL"); //$NON-NLS-1$ + ctx.init(km, tm, random); + ((HttpsURLConnection) wrappedUrlConnection).setSSLSocketFactory(ctx + .getSocketFactory()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java new file mode 100644 index 000000000..d1c875d40 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnectionFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.transport.http; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; + +/** + * A factory returning instances of {@link JDKHttpConnection} + * + * @since 3.3 + */ +public class JDKHttpConnectionFactory implements HttpConnectionFactory { + public HttpConnection create(URL url) throws IOException { + return new JDKHttpConnection(url); + } + + public HttpConnection create(URL url, Proxy proxy) + throws IOException { + return new JDKHttpConnection(url, proxy); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java index 0c41e945d..20366efdf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java @@ -47,7 +47,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.ConnectException; -import java.net.HttpURLConnection; import java.net.Proxy; import java.net.ProxySelector; import java.net.URISyntaxException; @@ -56,6 +55,7 @@ import java.text.MessageFormat; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.transport.http.HttpConnection; /** Extra utilities to support usage of HTTP. */ public class HttpSupport { @@ -158,11 +158,12 @@ public static void encode(final StringBuilder urlstr, final String key) { * @param c * connection the code should be obtained from. * @return r HTTP status code, usually 200 to indicate success. See - * {@link HttpURLConnection} for other defined constants. + * {@link HttpConnection} for other defined constants. * @throws IOException * communications error prevented obtaining the response code. + * @since 3.3 */ - public static int response(final HttpURLConnection c) throws IOException { + public static int response(final HttpConnection c) throws IOException { try { return c.getResponseCode(); } catch (ConnectException ce) { @@ -175,6 +176,34 @@ public static int response(final HttpURLConnection c) throws IOException { } } + /** + * Get the HTTP response code from the request. + *

+ * Roughly the same as c.getResponseCode() but the + * ConnectException is translated to be more understandable. + * + * @param c + * connection the code should be obtained from. + * @return r HTTP status code, usually 200 to indicate success. See + * {@link HttpConnection} for other defined constants. + * @throws IOException + * communications error prevented obtaining the response code. + */ + public static int response(final java.net.HttpURLConnection c) + throws IOException { + try { + return c.getResponseCode(); + } catch (ConnectException ce) { + final String host = c.getURL().getHost(); + // The standard J2SE error message is not very useful. + // + if ("Connection timed out: connect".equals(ce.getMessage())) + throw new ConnectException(MessageFormat.format( + JGitText.get().connectionTimeOut, host)); + throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$ + } + } + /** * Determine the proxy server (if any) needed to obtain a URL. *