diff options
author | Kevin Sawicki | 2011-12-12 23:36:44 +0000 |
---|---|---|
committer | Kevin Sawicki | 2011-12-12 23:36:44 +0000 |
commit | ca40854c3794b7e720e9d3762c3bbd45c5674c9b (patch) | |
tree | 9eecbcba3b99106958e63d6db51e6c13eaf6d50a | |
parent | 999a2fb99bf67025e38542003af0e5022b3b5b94 (diff) | |
download | egit-github-ca40854c3794b7e720e9d3762c3bbd45c5674c9b.tar.gz egit-github-ca40854c3794b7e720e9d3762c3bbd45c5674c9b.tar.xz egit-github-ca40854c3794b7e720e9d3762c3bbd45c5674c9b.zip |
Drop dependency on Apache HttpComponents (HC)
HC was used sparingly and added several additional JAR requirements
that made the GitHub Java API unusable on Android due to these
dependencies.
A HttpURLConnection is now used instead to make all request types.
Change-Id: I82e442139d3adec3d2331f279bde69320b13638d
Signed-off-by: Kevin Sawicki <kevin@github.com>
22 files changed, 951 insertions, 690 deletions
diff --git a/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/EncodingUtilsTest.java b/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/EncodingUtilsTest.java index 5f42868d..5b0b6da4 100644 --- a/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/EncodingUtilsTest.java +++ b/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/EncodingUtilsTest.java @@ -37,10 +37,10 @@ public class EncodingUtilsTest { @Test public void encodeDecode() { String test = "content"; - byte[] encoded = EncodingUtils.toBase64(test.getBytes()); + String encoded = EncodingUtils.toBase64(test.getBytes()); assertNotNull(encoded); - assertFalse(encoded.length == 0); - assertFalse(test.equals(new String(encoded))); + assertFalse(encoded.length() == 0); + assertFalse(test.equals(encoded)); byte[] decoded = EncodingUtils.fromBase64(new String(encoded)); assertNotNull(decoded); assertFalse(decoded.length == 0); diff --git a/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/GitHubClientTest.java b/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/GitHubClientTest.java index 818d0903..6eae112b 100644 --- a/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/GitHubClientTest.java +++ b/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/GitHubClientTest.java @@ -34,14 +34,6 @@ public class GitHubClientTest { } /** - * Create client with null host - */ - @Test(expected = IllegalArgumentException.class) - public void constructorNullArgument() { - new GitHubClient((HttpHost) null); - } - - /** * Verify prefix with API v2 host */ @Test diff --git a/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/live/LiveTest.java b/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/live/LiveTest.java index f7927bac..c6930239 100644 --- a/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/live/LiveTest.java +++ b/org.eclipse.egit.github.core.tests/src/org/eclipse/egit/github/core/tests/live/LiveTest.java @@ -4,7 +4,7 @@ * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html - * + * * Contributors: * Kevin Sawicki (GitHub Inc.) - initial API and implementation *******************************************************************************/ @@ -15,7 +15,6 @@ import static org.junit.Assert.assertNotNull; import java.io.IOException; import java.net.URL; -import org.apache.http.HttpHost; import org.eclipse.egit.github.core.client.GitHubClient; import org.junit.Before; @@ -36,7 +35,7 @@ public abstract class LiveTest { /** * Configure client - * + * * @param client * @return specified client */ @@ -50,7 +49,7 @@ public abstract class LiveTest { /** * Create client for url - * + * * @param url * @return client * @throws IOException @@ -59,9 +58,8 @@ public abstract class LiveTest { GitHubClient client = null; if (url != null) { URL parsed = new URL(url); - HttpHost httpHost = new HttpHost(parsed.getHost(), - parsed.getPort(), parsed.getProtocol()); - client = new GitHubClient(httpHost); + client = new GitHubClient(parsed.getHost(), parsed.getPort(), + parsed.getProtocol()); } else client = new GitHubClient(); return configure(client); @@ -69,7 +67,7 @@ public abstract class LiveTest { /** * Set up live unit test - * + * * @throws Exception */ @Before diff --git a/org.eclipse.egit.github.core/META-INF/MANIFEST.MF b/org.eclipse.egit.github.core/META-INF/MANIFEST.MF index 15c1acb1..3a54338e 100644 --- a/org.eclipse.egit.github.core/META-INF/MANIFEST.MF +++ b/org.eclipse.egit.github.core/META-INF/MANIFEST.MF @@ -8,25 +8,7 @@ Bundle-Vendor: %providerName Bundle-RequiredExecutionEnvironment: J2SE-1.5 Import-Package: com.google.gson;version="[1.6.0,2.0.0)", com.google.gson.annotations;version="[1.6.0,2.0.0)", - com.google.gson.reflect;version="[1.6.0,2.0.0)", - org.apache.commons.codec.binary;version="[1.3.0,2.0.0)", - org.apache.http;version="[4.1.0,5.0.0)", - org.apache.http.auth;version="[4.1.0,5.0.0)", - org.apache.http.client;version="[4.1.0,5.0.0)", - org.apache.http.client.methods;version="[4.1.0,5.0.0)", - org.apache.http.client.protocol;version="[4.1.0,5.0.0)", - org.apache.http.client.utils;version="[4.1.0,5.0.0)", - org.apache.http.conn;version="[4.1.0,5.0.0)", - org.apache.http.conn.routing;version="[4.1.0,5.0.0)", - org.apache.http.entity;version="[4.1.0,5.0.0)", - org.apache.http.entity.mime;version="[4.1.0,5.0.0)", - org.apache.http.entity.mime.content;version="[4.1.0,5.0.0)", - org.apache.http.impl.auth;version="[4.1.0,5.0.0)", - org.apache.http.impl.client;version="[4.1.0,5.0.0)", - org.apache.http.impl.conn;version="[4.1.0,5.0.0)", - org.apache.http.message;version="[4.1.0,5.0.0)", - org.apache.http.protocol;version="[4.1.0,5.0.0)", - org.apache.http.util;version="[4.1.0,5.0.0)" + com.google.gson.reflect;version="[1.6.0,2.0.0)" Export-Package: org.eclipse.egit.github.core;version="1.3.0", org.eclipse.egit.github.core.client;version="1.3.0", org.eclipse.egit.github.core.service;version="1.3.0", diff --git a/org.eclipse.egit.github.core/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.egit.github.core/META-INF/SOURCE-MANIFEST.MF index ea1f6efd..9e81c482 100644 --- a/org.eclipse.egit.github.core/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.egit.github.core/META-INF/SOURCE-MANIFEST.MF @@ -7,25 +7,7 @@ Bundle-Vendor: Eclipse EGit Bundle-RequiredExecutionEnvironment: J2SE-1.5 Import-Package: com.google.gson;version="[1.6.0,2.0.0)", com.google.gson.annotations;version="[1.6.0,2.0.0)", - com.google.gson.reflect;version="[1.6.0,2.0.0)", - org.apache.commons.codec.binary;version="[1.3.0,2.0.0)", - org.apache.http;version="[4.1.0,5.0.0)", - org.apache.http.auth;version="[4.1.0,5.0.0)", - org.apache.http.client;version="[4.1.0,5.0.0)", - org.apache.http.client.methods;version="[4.1.0,5.0.0)", - org.apache.http.client.protocol;version="[4.1.0,5.0.0)", - org.apache.http.client.utils;version="[4.1.0,5.0.0)", - org.apache.http.conn;version="[4.1.0,5.0.0)", - org.apache.http.conn.routing;version="[4.1.0,5.0.0)", - org.apache.http.entity;version="[4.1.0,5.0.0)", - org.apache.http.entity.mime;version="[4.1.0,5.0.0)", - org.apache.http.entity.mime.content;version="[4.1.0,5.0.0)", - org.apache.http.impl.auth;version="[4.1.0,5.0.0)", - org.apache.http.impl.client;version="[4.1.0,5.0.0)", - org.apache.http.impl.conn;version="[4.1.0,5.0.0)", - org.apache.http.message;version="[4.1.0,5.0.0)", - org.apache.http.protocol;version="[4.1.0,5.0.0)", - org.apache.http.util;version="[4.1.0,5.0.0)" + com.google.gson.reflect;version="[1.6.0,2.0.0)" Export-Package: org.eclipse.egit.github.core;version="1.1.0", org.eclipse.egit.github.core.client;version="1.1.0", org.eclipse.egit.github.core.service;version="1.1.0", diff --git a/org.eclipse.egit.github.core/pom-jar.xml b/org.eclipse.egit.github.core/pom-jar.xml index 36791d40..7ff0967d 100644 --- a/org.eclipse.egit.github.core/pom-jar.xml +++ b/org.eclipse.egit.github.core/pom-jar.xml @@ -173,24 +173,6 @@ <dependencies> <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpclient</artifactId> - <version>4.1.2</version> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpcore</artifactId> - <version>4.1.3</version> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpmime</artifactId> - <version>4.1.2</version> - <scope>compile</scope> - </dependency> - <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>1.7.2</version> diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/AuthInterceptor.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/AuthInterceptor.java deleted file mode 100644 index daf5cb70..00000000 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/AuthInterceptor.java +++ /dev/null @@ -1,61 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2011 GitHub Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Kevin Sawicki (GitHub Inc.) - initial API and implementation - *****************************************************************************/ -package org.eclipse.egit.github.core.client; - -import static org.apache.http.client.protocol.ClientContext.AUTH_CACHE; -import static org.apache.http.client.protocol.ClientContext.CREDS_PROVIDER; -import static org.apache.http.client.protocol.ClientContext.TARGET_AUTH_STATE; -import static org.apache.http.protocol.ExecutionContext.HTTP_TARGET_HOST; - -import java.io.IOException; - -import org.apache.http.HttpException; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.auth.AuthScheme; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.AuthState; -import org.apache.http.auth.Credentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.protocol.HttpContext; - -/** - * Interceptor that sets authentication credentials - */ -public class AuthInterceptor implements HttpRequestInterceptor { - - public void process(final HttpRequest request, final HttpContext context) - throws HttpException, IOException { - AuthState authState = (AuthState) context - .getAttribute(TARGET_AUTH_STATE); - if (authState == null || authState.getAuthScheme() != null) - return; - HttpHost targetHost = (HttpHost) context.getAttribute(HTTP_TARGET_HOST); - if (targetHost == null) - return; - AuthCache cache = (AuthCache) context.getAttribute(AUTH_CACHE); - AuthScheme authScheme = cache.get(targetHost); - if (authScheme == null) - return; - CredentialsProvider provider = (CredentialsProvider) context - .getAttribute(CREDS_PROVIDER); - if (provider == null) - return; - Credentials creds = provider.getCredentials(new AuthScope(targetHost - .getHostName(), targetHost.getPort())); - if (creds == null) - return; - authState.setAuthScheme(authScheme); - authState.setCredentials(creds); - } -} diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/EntityDeleteMethod.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/EntityDeleteMethod.java deleted file mode 100644 index f663602a..00000000 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/EntityDeleteMethod.java +++ /dev/null @@ -1,53 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2011 GitHub Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Kevin Sawicki (GitHub Inc.) - initial API and implementation - *****************************************************************************/ -package org.eclipse.egit.github.core.client; - -import java.net.URI; - -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; - -/** - * DELETE request that supports an enclosed entity - */ -public class EntityDeleteMethod extends HttpEntityEnclosingRequestBase { - - /** - * Create DELETE - */ - public EntityDeleteMethod() { - super(); - } - - /** - * Create DELETE - * - * @param uri - */ - public EntityDeleteMethod(final URI uri) { - super(); - setURI(uri); - } - - /** - * Create DELETE - * - * @param uri - */ - public EntityDeleteMethod(final String uri) { - super(); - setURI(URI.create(uri)); - } - - public String getMethod() { - return HttpDelete.METHOD_NAME; - } -} diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubClient.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubClient.java index 3344340a..e2fe7a47 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubClient.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubClient.java @@ -11,16 +11,14 @@ *******************************************************************************/ package org.eclipse.egit.github.core.client; -import static org.apache.http.HttpStatus.SC_BAD_REQUEST; -import static org.apache.http.HttpStatus.SC_CREATED; -import static org.apache.http.HttpStatus.SC_FORBIDDEN; -import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; -import static org.apache.http.HttpStatus.SC_NOT_FOUND; -import static org.apache.http.HttpStatus.SC_NO_CONTENT; -import static org.apache.http.HttpStatus.SC_OK; -import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; -import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY; -import static org.apache.http.client.protocol.ClientContext.AUTH_CACHE; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_CREATED; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.eclipse.egit.github.core.client.IGitHubConstants.AUTH_TOKEN; import static org.eclipse.egit.github.core.client.IGitHubConstants.CHARSET_UTF8; import static org.eclipse.egit.github.core.client.IGitHubConstants.CONTENT_TYPE_JSON; @@ -35,40 +33,17 @@ import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_V3_AP import com.google.gson.Gson; import com.google.gson.JsonParseException; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; -import java.net.ProxySelector; +import java.net.HttpURLConnection; import java.net.URL; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.HttpMessage; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; -import org.apache.http.message.BasicHeader; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.http.protocol.HTTP; -import org.apache.http.protocol.HttpContext; -import org.apache.http.util.EntityUtils; import org.eclipse.egit.github.core.RequestError; +import org.eclipse.egit.github.core.util.EncodingUtils; /** * Client class for interacting with GitHub HTTP/JSON API. @@ -96,23 +71,76 @@ public class GitHubClient { } } - private static final Header USER_AGENT = new BasicHeader(HTTP.USER_AGENT, - "GitHubJava/1.2.0"); //$NON-NLS-1$ + /** + * Content-Type header + */ + protected static final String HEADER_CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$ - private final HttpHost httpHost; + /** + * Accept header + */ + protected static final String HEADER_ACCEPT = "Accept"; //$NON-NLS-1$ - private final HttpContext httpContext; + /** + * Authorization header + */ + protected static final String HEADER_AUTHORIZATION = "Authorization"; //$NON-NLS-1$ - private final DefaultHttpClient client = new DefaultHttpClient(); + /** + * User-Agent header + */ + protected static final String HEADER_USER_AGENT = "User-Agent"; //$NON-NLS-1$ - private Header userAgent = USER_AGENT; + /** + * METHOD_GET + */ + protected static final String METHOD_GET = "GET"; //$NON-NLS-1$ - private final Header accept = new BasicHeader( - "Accept", "application/vnd.github.beta+json"); //$NON-NLS-1$ //$NON-NLS-2$ + /** + * METHOD_PUT + */ + protected static final String METHOD_PUT = "PUT"; //$NON-NLS-1$ - private final String prefix; + /** + * METHOD_POST + */ + protected static final String METHOD_POST = "POST"; //$NON-NLS-1$ - private Gson gson = GsonUtils.getGson(); + /** + * METHOD_DELETE + */ + protected static final String METHOD_DELETE = "DELETE"; //$NON-NLS-1$ + + /** + * Default user agent request header value + */ + protected static final String USER_AGENT = "GitHubJava/1.2.0"; //$NON-NLS-1$ + + /** + * 422 status code for unprocessable entity + */ + protected static final int HTTP_UNPROCESSABLE_ENTITY = 422; + + /** + * Base URI + */ + protected final String baseUri; + + /** + * Prefix to apply to base URI + */ + protected final String prefix; + + /** + * {@link Gson} instance + */ + protected Gson gson = GsonUtils.getGson(); + + private String user; + + private String credentials; + + private String userAgent; /** * Create default client @@ -137,38 +165,20 @@ public class GitHubClient { * @param port * @param scheme */ - public GitHubClient(String hostname, int port, String scheme) { - this(new HttpHost(hostname, port, scheme)); - } - - /** - * Create client for host configuration - * - * @param httpHost - */ - public GitHubClient(HttpHost httpHost) { - if (httpHost == null) - throw new IllegalArgumentException("Http host cannot be null"); //$NON-NLS-1$ - - this.httpHost = httpHost; + public GitHubClient(final String hostname, final int port, + final String scheme) { + final StringBuilder uri = new StringBuilder(scheme); + uri.append("://"); //$NON-NLS-1$ + uri.append(hostname); + if (port > 0) + uri.append(port); + baseUri = uri.toString(); // Use URI prefix on non-standard host names - String host = httpHost.getHostName(); - if (HOST_API.equals(host) || HOST_API_V2.equals(host)) + if (HOST_API.equals(hostname) || HOST_API_V2.equals(hostname)) prefix = null; else prefix = SEGMENT_V3_API; - - // Support JVM configured proxy servers - client.setRoutePlanner(new ProxySelectorRoutePlanner(client - .getConnectionManager().getSchemeRegistry(), ProxySelector - .getDefault())); - - // Preemptive authentication - httpContext = new BasicHttpContext(); - AuthCache authCache = new BasicAuthCache(); - httpContext.setAttribute(AUTH_CACHE, authCache); - client.addRequestInterceptor(new AuthInterceptor(), 0); } /** @@ -190,9 +200,9 @@ public class GitHubClient { * @param agent * @return this client */ - public GitHubClient setUserAgent(String agent) { + public GitHubClient setUserAgent(final String agent) { if (agent != null && agent.length() > 0) - userAgent = new BasicHeader(HTTP.USER_AGENT, agent); + userAgent = agent; else userAgent = USER_AGENT; return this; @@ -204,9 +214,12 @@ public class GitHubClient { * @param request * @return configured request */ - protected <V extends HttpMessage> V configureRequest(V request) { - request.addHeader(userAgent); - request.addHeader(accept); + protected HttpURLConnection configureRequest(final HttpURLConnection request) { + if (credentials != null) + request.setRequestProperty(HEADER_AUTHORIZATION, credentials); + request.setRequestProperty(HEADER_USER_AGENT, userAgent); + request.setRequestProperty(HEADER_ACCEPT, + "application/vnd.github.beta+json"); //$NON-NLS-1$ return request; } @@ -224,60 +237,74 @@ public class GitHubClient { } /** - * Create standard post method + * Create connection to URI * * @param uri - * @return post + * @return connection + * @throws IOException */ - protected HttpPost createPost(final String uri) { - return configureRequest(new HttpPost(configureUri(uri))); + protected HttpURLConnection createConnection(String uri) throws IOException { + URL url = new URL(createUri(uri)); + return (HttpURLConnection) url.openConnection(); } /** - * Create standard put method + * Create connection to URI * * @param uri - * @return post + * @param method + * @return connection + * @throws IOException */ - protected HttpPut createPut(final String uri) { - return configureRequest(new HttpPut(configureUri(uri))); + protected HttpURLConnection createConnection(String uri, String method) + throws IOException { + HttpURLConnection connection = createConnection(uri); + connection.setRequestMethod(method); + return configureRequest(connection); } /** - * Create get method + * Create a GET request connection to the URI * * @param uri - * @return get method + * @return connection + * @throws IOException */ - protected HttpGet createGet(final String uri) { - return configureRequest(new HttpGet(configureUri(uri))); + protected HttpURLConnection createGet(String uri) throws IOException { + return createConnection(uri, METHOD_GET); } /** - * Create delete method + * Create a POST request connection to the URI * * @param uri - * @return get method + * @return connection + * @throws IOException */ - protected HttpDelete createDelete(String uri) { - return configureRequest(new HttpDelete(configureUri(uri))); + protected HttpURLConnection createPost(String uri) throws IOException { + return createConnection(uri, METHOD_POST); } /** - * Update credential on HTTP client credentials provider + * Create a PUT request connection to the URI * - * @param user - * @param password - * @return this client + * @param uri + * @return connection + * @throws IOException */ - protected GitHubClient updateCredentials(String user, String password) { - if (user != null && password != null) - client.getCredentialsProvider().setCredentials( - new AuthScope(httpHost.getHostName(), httpHost.getPort()), - new UsernamePasswordCredentials(user, password)); - else - client.getCredentialsProvider().clear(); - return this; + protected HttpURLConnection createPut(String uri) throws IOException { + return createConnection(uri, METHOD_PUT); + } + + /** + * Create a DELETE request connection to the URI + * + * @param uri + * @return connection + * @throws IOException + */ + protected HttpURLConnection createDelete(String uri) throws IOException { + return createConnection(uri, METHOD_DELETE); } /** @@ -287,10 +314,14 @@ public class GitHubClient { * @param password * @return this client */ - public GitHubClient setCredentials(String user, String password) { - updateCredentials(user, password); - AuthCache authCache = (AuthCache) httpContext.getAttribute(AUTH_CACHE); - authCache.put(httpHost, new BasicScheme()); + public GitHubClient setCredentials(final String user, final String password) { + this.user = user; + if (user != null && user.length() > 0 && password != null + && password.length() > 0) + credentials = "Basic " //$NON-NLS-1$ + + EncodingUtils.toBase64(user + ':' + password); + else + credentials = null; return this; } @@ -301,9 +332,10 @@ public class GitHubClient { * @return this client */ public GitHubClient setOAuth2Token(String token) { - updateCredentials(AUTH_TOKEN, token); - AuthCache authCache = (AuthCache) httpContext.getAttribute(AUTH_CACHE); - authCache.put(httpHost, new OAuth2Scheme()); + if (token != null && token.length() > 0) + credentials = AUTH_TOKEN + ' ' + token; + else + credentials = null; return this; } @@ -313,27 +345,36 @@ public class GitHubClient { * @return user or null if not authentication */ public String getUser() { - Credentials credentials = client.getCredentialsProvider() - .getCredentials( - new AuthScope(httpHost.getHostName(), httpHost - .getPort())); - return credentials != null ? credentials.getUserPrincipal().getName() - : null; + return user; + } + + /** + * Convert object to a JSON string + * + * @param object + * @return JSON string + * @throws IOException + */ + protected String toJson(Object object) throws IOException { + try { + return gson.toJson(object); + } catch (JsonParseException jpe) { + throw new IOException(jpe.getMessage()); + } } /** * Parse JSON to specified type * * @param <V> - * @param response + * @param stream * @param type - * @return type + * @return parsed type * @throws IOException */ - protected <V> V parseJson(HttpResponse response, Type type) - throws IOException { - InputStreamReader reader = new InputStreamReader(getStream(response), - CHARSET_UTF8); + protected <V> V parseJson(InputStream stream, Type type) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader( + stream, CHARSET_UTF8)); try { return gson.fromJson(reader, type); } catch (JsonParseException jpe) { @@ -348,46 +389,49 @@ public class GitHubClient { } /** - * Convert object to a JSON string + * Does status code denote an error * - * @param object - * @return JSON string - * @throws IOException + * @param code + * @return true if error, false otherwise */ - private String toJson(Object object) throws IOException { - try { - return gson.toJson(object); - } catch (JsonParseException jpe) { - throw new IOException(jpe.getMessage()); + protected boolean isError(final int code) { + switch (code) { + case HTTP_BAD_REQUEST: + case HTTP_UNAUTHORIZED: + case HTTP_FORBIDDEN: + case HTTP_NOT_FOUND: + case HTTP_UNPROCESSABLE_ENTITY: + case HTTP_INTERNAL_ERROR: + return true; + default: + return false; } } /** - * Get {@link HttpEntity} from response + * Does status code denote a non-error response? * - * @param response - * @return non-null entity - * @throws IOException + * @param code + * @return true if okay, false otherwise */ - protected HttpEntity getEntity(HttpResponse response) throws IOException { - HttpEntity entity = response.getEntity(); - if (entity == null) - throw new IOException("Response has no entity"); //$NON-NLS-1$ - return entity; + protected boolean isOk(final int code) { + switch (code) { + case HTTP_OK: + case HTTP_CREATED: + return true; + default: + return false; + } } /** - * Get {@link InputStream} from response + * Is the response empty? * - * @param response - * @return non-null input stream - * @throws IOException + * @param code + * @return true if empty, false otherwise */ - protected InputStream getStream(HttpResponse response) throws IOException { - InputStream stream = getEntity(response).getContent(); - if (stream == null) - throw new IOException("Empty body"); //$NON-NLS-1$ - return stream; + protected boolean isEmpty(final int code) { + return HTTP_NO_CONTENT == code; } /** @@ -397,224 +441,207 @@ public class GitHubClient { * @return request error * @throws IOException */ - protected RequestError parseError(HttpResponse response) throws IOException { + protected RequestError parseError(InputStream response) throws IOException { return parseJson(response, RequestError.class); } /** + * Get body from response inputs stream + * + * @param request + * @param stream + * @return parsed body + * @throws IOException + */ + protected Object getBody(GitHubRequest request, InputStream stream) + throws IOException { + Type type = request.getType(); + if (type != null) + return parseJson(stream, type); + else + return null; + } + + /** * Create error exception from response and throw it * * @param response + * @param code * @param status * @return non-null newly created {@link IOException} */ - protected IOException createException(HttpResponse response, - StatusLine status) { - final int code = status.getStatusCode(); - switch (code) { - case SC_BAD_REQUEST: - case SC_UNAUTHORIZED: - case SC_FORBIDDEN: - case SC_NOT_FOUND: - case SC_UNPROCESSABLE_ENTITY: - case SC_INTERNAL_SERVER_ERROR: - RequestError error; + protected IOException createException(InputStream response, int code, + String status) { + if (isError(code)) { + final RequestError error; try { error = parseError(response); } catch (IOException e) { return e; } return new RequestException(error, code); - default: - return new IOException(status.getReasonPhrase()); - } + } else + return new IOException(status); } /** - * Is the response successful? + * Post to URI * - * @param response - * @param status - * @return true if okay, false otherwise + * @param uri + * @throws IOException */ - protected boolean isOk(HttpResponse response, StatusLine status) { - switch (status.getStatusCode()) { - case SC_OK: - case SC_CREATED: - return true; - default: - return false; - } + public void post(String uri) throws IOException { + post(uri, null, null); } /** - * Is the response empty? + * Put to URI * - * @param response - * @param status - * @return true if empty, false otherwise + * @param uri + * @throws IOException */ - protected boolean isEmpty(HttpResponse response, StatusLine status) { - return SC_NO_CONTENT == status.getStatusCode(); + public void put(String uri) throws IOException { + put(uri, null, null); } /** - * Get status line from response + * Delete resource at URI. This method will throw an {@link IOException} + * when the response status is not a 204 (No Content). * - * @param response - * @return Non-null status line + * @param uri * @throws IOException */ - protected StatusLine getStatus(HttpResponse response) throws IOException { - StatusLine statusLine = response.getStatusLine(); - if (statusLine == null) - throw new IOException("Empty HTTP response status line"); //$NON-NLS-1$ - return statusLine; + public void delete(String uri) throws IOException { + delete(uri, null); } /** - * Get response stream from URI. It is the responsibility of the calling - * method to close the returned stream. + * Send parameters to output stream of request * * @param request - * @return stream + * @param params * @throws IOException */ - public InputStream getStream(GitHubRequest request) throws IOException { - HttpGet method = createGet(request.generateUri()); - HttpResponse response = client.execute(httpHost, method, httpContext); - StatusLine status = getStatus(response); - if (isOk(response, status)) - return getStream(response); - throw createException(response, status); + protected void sendParams(HttpURLConnection request, Object params) + throws IOException { + request.setDoOutput(true); + request.setRequestProperty(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON + + "; charset=" + CHARSET_UTF8); //$NON-NLS-1$ + byte[] data = toJson(params).getBytes(CHARSET_UTF8); + request.setFixedLengthStreamingMode(data.length); + BufferedOutputStream output = new BufferedOutputStream( + request.getOutputStream()); + try { + output.write(data); + } finally { + output.close(); + } } - /** - * Get response from URI and bind to specified type - * - * @param request - * @return response - * @throws IOException - */ - public GitHubResponse get(GitHubRequest request) throws IOException { - HttpGet method = createGet(request.generateUri()); - HttpResponse response = client.execute(httpHost, method, httpContext); - StatusLine status = getStatus(response); - if (isOk(response, status)) { - Object body = null; - Type type = request.getType(); + private <V> V sendJson(final HttpURLConnection request, + final Object params, final Type type) throws IOException { + if (params != null) + sendParams(request, params); + final int code = request.getResponseCode(); + if (isOk(code)) if (type != null) - body = parseJson(response, type); + return parseJson(getStream(request), type); else - EntityUtils.consume(response.getEntity()); - return new GitHubResponse(response, body); - } - if (isEmpty(response, status)) - return new GitHubResponse(response, null); - throw createException(response, status); + return null; + if (isEmpty(code)) + return null; + throw createException(getStream(request), code, + request.getResponseMessage()); } /** - * Send JSON using specified method + * Create full URI from path * - * @param <V> - * @param method - * @param params - * @param type - * @return resource - * @throws IOException + * @param path + * @return uri */ - protected <V> V sendJson(HttpEntityEnclosingRequestBase method, - Object params, Type type) throws IOException { - if (params != null) - method.setEntity(new StringEntity(toJson(params), - CONTENT_TYPE_JSON, CHARSET_UTF8)); - HttpResponse response = client.execute(httpHost, method, httpContext); - StatusLine status = getStatus(response); - if (isOk(response, status)) { - if (type != null) - return parseJson(response, type); - EntityUtils.consume(response.getEntity()); - return null; - } - if (isEmpty(response, status)) - return null; - throw createException(response, status); + protected String createUri(final String path) { + return baseUri + configureUri(path); } /** - * Post data to URI + * Get response stream from URI. It is the responsibility of the calling + * method to close the returned stream. * - * @param <V> - * @param uri - * @param params - * @param type - * @return response + * @param request + * @return stream * @throws IOException */ - public <V> V post(String uri, Object params, Type type) throws IOException { - return sendJson(createPost(uri), params, type); + public InputStream getStream(GitHubRequest request) throws IOException { + return getStream(createGet(request.generateUri())); } /** - * Post to URI + * Get stream from request * - * @param uri + * @param request + * @return stream * @throws IOException */ - public void post(String uri) throws IOException { - post(uri, null, null); + protected InputStream getStream(HttpURLConnection request) + throws IOException { + if (request.getResponseCode() < HTTP_BAD_REQUEST) + return request.getInputStream(); + else { + InputStream stream = request.getErrorStream(); + return stream != null ? stream : request.getInputStream(); + } } /** - * Put data to URI + * Get response from URI and bind to specified type * - * @param <V> - * @param uri - * @param params - * @param type + * @param request * @return response * @throws IOException */ - public <V> V put(String uri, Object params, Type type) throws IOException { - return sendJson(createPut(uri), params, type); + public GitHubResponse get(GitHubRequest request) throws IOException { + HttpURLConnection httpRequest = createGet(request.generateUri()); + final int code = httpRequest.getResponseCode(); + if (isOk(code)) + return new GitHubResponse(httpRequest, getBody(request, + getStream(httpRequest))); + if (isEmpty(code)) + return new GitHubResponse(httpRequest, null); + throw createException(getStream(httpRequest), code, + httpRequest.getResponseMessage()); } /** - * Put to URI + * Post data to URI * + * @param <V> * @param uri + * @param params + * @param type + * @return response * @throws IOException */ - public void put(String uri) throws IOException { - put(uri, null, null); + public <V> V post(final String uri, final Object params, final Type type) + throws IOException { + HttpURLConnection request = createPost(uri); + return sendJson(request, params, type); } /** - * Delete resource at URI. This method will throw an {@link IOException} - * when the response status is not a 204 (No Content). + * Put data to URI * + * @param <V> * @param uri * @param params + * @param type + * @return response * @throws IOException */ - public void delete(String uri, Object params) throws IOException { - HttpRequest method; - if (params == null) - method = createDelete(uri); - else { - EntityDeleteMethod delete = configureRequest(new EntityDeleteMethod( - uri)); - delete.setEntity(new StringEntity(toJson(params), - CONTENT_TYPE_JSON, CHARSET_UTF8)); - method = delete; - } - - HttpResponse response = client.execute(httpHost, method, httpContext); - StatusLine status = getStatus(response); - if (!isEmpty(response, status)) - throw new RequestException(parseError(response), - status.getStatusCode()); + public <V> V put(final String uri, final Object params, final Type type) + throws IOException { + HttpURLConnection request = createPut(uri); + return sendJson(request, params, type); } /** @@ -622,9 +649,16 @@ public class GitHubClient { * when the response status is not a 204 (No Content). * * @param uri + * @param params * @throws IOException */ - public void delete(String uri) throws IOException { - delete(uri, null); + public void delete(final String uri, final Object params) + throws IOException { + HttpURLConnection request = createDelete(uri); + if (params != null) + sendParams(request, params); + final int code = request.getResponseCode(); + if (!isEmpty(code)) + throw new RequestException(parseError(getStream(request)), code); } } diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubRequest.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubRequest.java index c558c557..9a69cdcb 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubRequest.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubRequest.java @@ -11,14 +11,9 @@ package org.eclipse.egit.github.core.client; import java.lang.reflect.Type; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicNameValuePair; +import org.eclipse.egit.github.core.util.UrlUtils; /** * GitHub API request class that contains the URI and parameters of the request @@ -50,18 +45,12 @@ public class GitHubRequest { } /** - * Get name value pairs for data map. + * Add request params to URI * - * @param data - * @return name value pair array + * @param uri */ - protected List<NameValuePair> getPairs(Map<String, String> data) { - List<NameValuePair> pairs = new LinkedList<NameValuePair>(); - if (data != null && !data.isEmpty()) - for (Entry<String, String> entry : data.entrySet()) - pairs.add(new BasicNameValuePair(entry.getKey(), entry - .getValue())); - return pairs; + protected void addParams(final StringBuilder uri) { + UrlUtils.addParams(getParams(), uri); } /** @@ -75,8 +64,9 @@ public class GitHubRequest { return null; if (baseUri.indexOf('?') != -1) return baseUri; - String params = URLEncodedUtils.format(getPairs(getParams()), null); - if (params != null && params.length() > 0) + final StringBuilder params = new StringBuilder(); + addParams(params); + if (params.length() > 0) return baseUri + '?' + params; else return baseUri; diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubResponse.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubResponse.java index 8b713feb..df426edc 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubResponse.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/GitHubResponse.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.eclipse.egit.github.core.client; -import org.apache.http.HttpResponse; +import java.net.HttpURLConnection; /** * GitHub API response class that provides the parsed response body as well as @@ -18,9 +18,20 @@ import org.apache.http.HttpResponse; */ public class GitHubResponse { - private PageLinks links; + /** + * HTTP response + */ + protected final HttpURLConnection response; - private Object body; + /** + * Response body + */ + protected final Object body; + + /** + * Links to other pages + */ + protected PageLinks links; /** * Create response @@ -28,18 +39,39 @@ public class GitHubResponse { * @param response * @param body */ - public GitHubResponse(HttpResponse response, Object body) { - links = new PageLinks(response); + public GitHubResponse(HttpURLConnection response, Object body) { + this.response = response; this.body = body; } /** + * Get header value + * + * @param name + * @return value + */ + public String getHeader(String name) { + return response.getHeaderField(name); + } + + /** + * Get page links + * + * @return links + */ + protected PageLinks getLinks() { + if (links == null) + links = new PageLinks(this); + return links; + } + + /** * Get link uri to first page * * @return possibly null uri */ public String getFirst() { - return links.getFirst(); + return getLinks().getFirst(); } /** @@ -48,7 +80,7 @@ public class GitHubResponse { * @return possibly null uri */ public String getPrevious() { - return links.getPrev(); + return getLinks().getPrev(); } /** @@ -57,7 +89,7 @@ public class GitHubResponse { * @return possibly null uri */ public String getNext() { - return links.getNext(); + return getLinks().getNext(); } /** @@ -66,7 +98,7 @@ public class GitHubResponse { * @return possibly null uri */ public String getLast() { - return links.getLast(); + return getLinks().getLast(); } /** diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/IGitHubConstants.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/IGitHubConstants.java index 1f313748..50bb83e2 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/IGitHubConstants.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/IGitHubConstants.java @@ -21,6 +21,8 @@ public interface IGitHubConstants { /** */ String CHARSET_UTF8 = "UTF-8"; //$NON-NLS-1$ /** */ + String CHARSET_ISO_8859_1 = "ISO-8859-1"; //$NON-NLS-1$ + /** */ String CONTENT_TYPE_JSON = "application/json"; //$NON-NLS-1$ /** */ diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/OAuth2Scheme.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/OAuth2Scheme.java deleted file mode 100644 index 47cac7b4..00000000 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/OAuth2Scheme.java +++ /dev/null @@ -1,57 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2011 GitHub Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Kevin Sawicki (GitHub Inc.) - initial API and implementation - *****************************************************************************/ -package org.eclipse.egit.github.core.client; - -import static org.apache.http.HttpHeaders.AUTHORIZATION; -import static org.eclipse.egit.github.core.client.IGitHubConstants.SCHEME_OAUTH2; - -import org.apache.http.Header; -import org.apache.http.HttpRequest; -import org.apache.http.auth.AuthenticationException; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.MalformedChallengeException; -import org.apache.http.impl.auth.RFC2617Scheme; -import org.apache.http.message.BasicHeader; - -/** - * OAuth2 authorization scheme. Sets an authorization header with a value of the - * space-separated user principal name and password from credentials. - */ -public class OAuth2Scheme extends RFC2617Scheme { - - private boolean complete = false; - - public void processChallenge(Header header) - throws MalformedChallengeException { - super.processChallenge(header); - complete = true; - } - - public String getSchemeName() { - return SCHEME_OAUTH2; - } - - public boolean isConnectionBased() { - return false; - } - - public boolean isComplete() { - return complete; - } - - public Header authenticate(Credentials credentials, HttpRequest request) - throws AuthenticationException { - if (credentials == null) - throw new IllegalArgumentException("Credentials cannot be null"); //$NON-NLS-1$ - return new BasicHeader(AUTHORIZATION, credentials.getUserPrincipal() - .getName() + ' ' + credentials.getPassword()); - } -} diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageIterator.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageIterator.java index 20f7a574..1a10cce9 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageIterator.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageIterator.java @@ -22,9 +22,8 @@ import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; import org.eclipse.egit.github.core.IResourceProvider; +import org.eclipse.egit.github.core.util.UrlUtils; /** * Iterator for getting paged responses. Each call to {@link #next()} will make @@ -95,16 +94,20 @@ public class PageIterator<V> implements Iterator<Collection<V>>, protected int parsePageNumber(String uri) { if (uri == null || uri.length() == 0) return -1; + final URI parsed; try { - for (NameValuePair pair : URLEncodedUtils.parse(new URI(uri), null)) - if (PARAM_PAGE.equals(pair.getName())) - return Integer.parseInt(pair.getValue()); + parsed = new URI(uri); } catch (URISyntaxException e) { return -1; + } + final String param = UrlUtils.getParam(parsed, PARAM_PAGE); + if (param == null || param.length() == 0) + return -1; + try { + return Integer.parseInt(param); } catch (NumberFormatException nfe) { return -1; } - return -1; } /** diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageLinks.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageLinks.java index 6d9bbabd..0fa3dff1 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageLinks.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PageLinks.java @@ -19,9 +19,6 @@ import static org.eclipse.egit.github.core.client.IGitHubConstants.META_NEXT; import static org.eclipse.egit.github.core.client.IGitHubConstants.META_PREV; import static org.eclipse.egit.github.core.client.IGitHubConstants.META_REL; -import org.apache.http.Header; -import org.apache.http.HttpResponse; - /** * Page link class to be used to determine the links to other pages of request * responses encoded in the current response. These will be present if the @@ -43,10 +40,10 @@ public class PageLinks { * * @param response */ - public PageLinks(HttpResponse response) { - Header[] linkHeaders = response.getHeaders(HEADER_LINK); - if (linkHeaders.length > 0) { - String[] links = linkHeaders[0].getValue().split(DELIM_LINKS); + public PageLinks(GitHubResponse response) { + String linkHeader = response.getHeader(HEADER_LINK); + if (linkHeader != null) { + String[] links = linkHeader.split(DELIM_LINKS); for (String link : links) { String[] segments = link.split(DELIM_LINK_PARAM); if (segments.length < 2) @@ -77,13 +74,8 @@ public class PageLinks { } } } else { - Header[] nextHeaders = response.getHeaders(HEADER_NEXT); - if (nextHeaders.length > 0) - next = nextHeaders[0].getValue(); - - Header[] lastHeaders = response.getHeaders(HEADER_LAST); - if (lastHeaders.length > 0) - last = lastHeaders[0].getValue(); + next = response.getHeader(HEADER_NEXT); + last = response.getHeader(HEADER_LAST); } } diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PagedRequest.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PagedRequest.java index 15238b1b..4ebe2022 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PagedRequest.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/client/PagedRequest.java @@ -13,11 +13,7 @@ package org.eclipse.egit.github.core.client; import static org.eclipse.egit.github.core.client.IGitHubConstants.PARAM_PAGE; import static org.eclipse.egit.github.core.client.IGitHubConstants.PARAM_PER_PAGE; -import java.util.List; -import java.util.Map; - -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; +import org.eclipse.egit.github.core.util.UrlUtils; /** * Paged request class that contains the initial page size and page number of @@ -69,17 +65,14 @@ public class PagedRequest<V> extends GitHubRequest { } @Override - protected List<NameValuePair> getPairs(Map<String, String> data) { - List<NameValuePair> pairs = super.getPairs(data); - int size = getPageSize(); + protected void addParams(final StringBuilder uri) { + super.addParams(uri); + final int size = getPageSize(); if (size > 0) - pairs.add(new BasicNameValuePair(PARAM_PER_PAGE, Integer - .toString(size))); - int number = getPage(); + UrlUtils.addParam(PARAM_PER_PAGE, Integer.toString(size), uri); + final int number = getPage(); if (number > 0) - pairs.add(new BasicNameValuePair(PARAM_PAGE, Integer - .toString(number))); - return pairs; + UrlUtils.addParam(PARAM_PAGE, Integer.toString(number), uri); } /** diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/DownloadService.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/DownloadService.java index 1a4caa0a..97c44421 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/DownloadService.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/DownloadService.java @@ -10,6 +10,7 @@ *****************************************************************************/ package org.eclipse.egit.github.core.service; +import static java.net.HttpURLConnection.HTTP_CREATED; import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_DOWNLOADS; import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_REPOS; import static org.eclipse.egit.github.core.client.PagedRequest.PAGE_FIRST; @@ -21,19 +22,11 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.ProxySelector; +import java.net.HttpURLConnection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.mime.MultipartEntity; -import org.apache.http.entity.mime.content.InputStreamBody; -import org.apache.http.entity.mime.content.StringBody; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; import org.eclipse.egit.github.core.Download; import org.eclipse.egit.github.core.DownloadResource; import org.eclipse.egit.github.core.IRepositoryIdProvider; @@ -41,6 +34,7 @@ import org.eclipse.egit.github.core.client.GitHubClient; import org.eclipse.egit.github.core.client.GitHubRequest; import org.eclipse.egit.github.core.client.PageIterator; import org.eclipse.egit.github.core.client.PagedRequest; +import org.eclipse.egit.github.core.util.MultiPartUtils; /** * Service for accessing, creating, and deleting repositories downloads. @@ -90,23 +84,10 @@ public class DownloadService extends GitHubService { */ public static final String UPLOAD_FILE = "file"; //$NON-NLS-1$ - private static class SizedInputStreamBody extends InputStreamBody { - - private final long size; - - /** - * @param in - * @param size - */ - public SizedInputStreamBody(InputStream in, long size) { - super(in, null); - this.size = size; - } - - public long getContentLength() { - return size; - } - } + /** + * UPLOAD_CONTENT_TYPE + */ + public static final String UPLOAD_CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$ /** * Create download service @@ -251,19 +232,6 @@ public class DownloadService extends GitHubService { } /** - * Create client to use to upload a resource to - * - * @return non-null http client - */ - protected HttpClient createDownloadClient() { - DefaultHttpClient client = new DefaultHttpClient(); - client.setRoutePlanner(new ProxySelectorRoutePlanner(client - .getConnectionManager().getSchemeRegistry(), ProxySelector - .getDefault())); - return client; - } - - /** * Upload a resource to be available as the download described by the given * resource. * @@ -281,28 +249,21 @@ public class DownloadService extends GitHubService { throw new IllegalArgumentException( "Content input stream cannot be null"); //$NON-NLS-1$ - HttpClient client = createDownloadClient(); - - HttpPost post = new HttpPost(resource.getS3Url()); - MultipartEntity entity = new MultipartEntity(); - entity.addPart(UPLOAD_KEY, new StringBody(resource.getPath())); - entity.addPart(UPLOAD_ACL, new StringBody(resource.getAcl())); - entity.addPart(UPLOAD_SUCCESS_ACTION_STATUS, - new StringBody(Integer.toString(HttpStatus.SC_CREATED))); - entity.addPart(UPLOAD_FILENAME, new StringBody(resource.getName())); - entity.addPart(UPLOAD_AWS_ACCESS_KEY_ID, - new StringBody(resource.getAccesskeyid())); - entity.addPart(UPLOAD_POLICY, new StringBody(resource.getPolicy())); - entity.addPart(UPLOAD_SIGNATURE, - new StringBody(resource.getSignature())); - entity.addPart(HttpHeaders.CONTENT_TYPE, - new StringBody(resource.getMimeType())); - entity.addPart(UPLOAD_FILE, new SizedInputStreamBody(content, size)); - post.setEntity(entity); - - HttpResponse response = client.execute(post); - int status = response.getStatusLine().getStatusCode(); - if (status != HttpStatus.SC_CREATED) + Map<String, Object> parts = new LinkedHashMap<String, Object>(); + parts.put(UPLOAD_KEY, resource.getPath()); + parts.put(UPLOAD_ACL, resource.getAcl()); + parts.put(UPLOAD_SUCCESS_ACTION_STATUS, Integer.toString(HTTP_CREATED)); + parts.put(UPLOAD_FILENAME, resource.getName()); + parts.put(UPLOAD_AWS_ACCESS_KEY_ID, resource.getAccesskeyid()); + parts.put(UPLOAD_POLICY, resource.getPolicy()); + parts.put(UPLOAD_SIGNATURE, resource.getSignature()); + parts.put(UPLOAD_CONTENT_TYPE, resource.getMimeType()); + parts.put(UPLOAD_FILE, content); + + HttpURLConnection connection = MultiPartUtils.post(resource.getS3Url(), + parts); + int status = connection.getResponseCode(); + if (status != HTTP_CREATED) throw new IOException("Unexpected response status of " + status); //$NON-NLS-1$ } @@ -320,13 +281,15 @@ public class DownloadService extends GitHubService { * raw content of the download * @param size * size of content in the input stream + * @return created resource * @throws IOException */ - public void createDownload(IRepositoryIdProvider repository, + public DownloadResource createDownload(IRepositoryIdProvider repository, Download download, InputStream content, long size) throws IOException { DownloadResource resource = createResource(repository, download); uploadResource(resource, content, size); + return resource; } /** @@ -338,14 +301,15 @@ public class DownloadService extends GitHubService { * metadata about the download * @param file * must be non-null + * @return created resource * @throws IOException */ - public void createDownload(IRepositoryIdProvider repository, + public DownloadResource createDownload(IRepositoryIdProvider repository, Download download, File file) throws IOException { if (file == null) throw new IllegalArgumentException("File cannot be null"); //$NON-NLS-1$ - createDownload(repository, download, new FileInputStream(file), + return createDownload(repository, download, new FileInputStream(file), file.length()); } } diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/GitHubService.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/GitHubService.java index fd53c2be..78b13e91 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/GitHubService.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/service/GitHubService.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.eclipse.egit.github.core.service; -import static org.apache.http.HttpStatus.SC_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static org.eclipse.egit.github.core.client.PagedRequest.PAGE_FIRST; import static org.eclipse.egit.github.core.client.PagedRequest.PAGE_SIZE; @@ -152,7 +152,7 @@ public abstract class GitHubService { client.get(createRequest().setUri(uri)); return true; } catch (RequestException e) { - if (e.getStatus() == SC_NOT_FOUND) + if (e.getStatus() == HTTP_NOT_FOUND) return false; throw e; } diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/Base64.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/Base64.java new file mode 100644 index 00000000..587d52c9 --- /dev/null +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/Base64.java @@ -0,0 +1,312 @@ +// +// NOTE: The following source code is heavily derived from the +// iHarder.net public domain Base64 library. See the original at +// http://iharder.sourceforge.net/current/java/base64/ +// + +package org.eclipse.egit.github.core.util; + +import static org.eclipse.egit.github.core.client.IGitHubConstants.CHARSET_UTF8; + +import java.io.UnsupportedEncodingException; +import java.text.MessageFormat; +import java.util.Arrays; + +/** + * Encodes and decodes to and from Base64 notation. + * <p> + * I am placing this code in the Public Domain. Do with it as you will. This + * software comes with no guarantees or warranties but with plenty of + * well-wishing instead! Please visit <a + * href="http://iharder.net/base64">http://iharder.net/base64</a> periodically + * to check for updates or to contribute improvements. + * </p> + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.1, stripped to minimum feature set used by JGit. + */ +class Base64 { + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** Indicates equals sign in encoding. */ + private final static byte EQUALS_SIGN_DEC = -1; + + /** Indicates white space in encoding. */ + private final static byte WHITE_SPACE_DEC = -2; + + /** Indicates an invalid byte during decoding. */ + private final static byte INVALID_DEC = -3; + + /** The 64 valid Base64 values. */ + private final static byte[] ENC; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value or a + * negative number indicating some other meaning. The table is only 7 bits + * wide, as the 8th bit is discarded during decoding. + */ + private final static byte[] DEC; + + static { + try { + ENC = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" // + + "abcdefghijklmnopqrstuvwxyz" // + + "0123456789" // + + "+/" // + ).getBytes(CHARSET_UTF8); + } catch (UnsupportedEncodingException uee) { + throw new RuntimeException(uee.getMessage(), uee); + } + + DEC = new byte[128]; + Arrays.fill(DEC, INVALID_DEC); + + for (int i = 0; i < 64; i++) + DEC[ENC[i]] = (byte) i; + DEC[EQUALS_SIGN] = EQUALS_SIGN_DEC; + + DEC['\t'] = WHITE_SPACE_DEC; + DEC['\n'] = WHITE_SPACE_DEC; + DEC['\r'] = WHITE_SPACE_DEC; + DEC[' '] = WHITE_SPACE_DEC; + } + + /** Defeats instantiation. */ + private Base64() { + // Suppress empty block warning. + } + + /** + * Encodes up to three bytes of the array <var>source</var> and writes the + * resulting four Base64 bytes to <var>destination</var>. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying <var>srcOffset</var> and <var>destOffset</var>. This method + * does not check to make sure your arrays are large enough to accommodate + * <var>srcOffset</var> + 3 for the <var>source</var> array or + * <var>destOffset</var> + 4 for the <var>destination</var> array. The + * actual number of significant bytes in your array is given by + * <var>numSigBytes</var>. + * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param numSigBytes + * the number of significant bytes in your array + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + */ + private static void encode3to4(byte[] source, int srcOffset, + int numSigBytes, byte[] destination, int destOffset) { + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte. + + int inBuff = 0; + switch (numSigBytes) { + case 3: + inBuff |= (source[srcOffset + 2] << 24) >>> 24; + //$FALL-THROUGH$ + + case 2: + inBuff |= (source[srcOffset + 1] << 24) >>> 16; + //$FALL-THROUGH$ + + case 1: + inBuff |= (source[srcOffset] << 24) >>> 8; + } + + switch (numSigBytes) { + case 3: + destination[destOffset] = ENC[(inBuff >>> 18)]; + destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ENC[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ENC[(inBuff) & 0x3f]; + break; + + case 2: + destination[destOffset] = ENC[(inBuff >>> 18)]; + destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ENC[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + break; + + case 1: + destination[destOffset] = ENC[(inBuff >>> 18)]; + destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + break; + } + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source + * The data to convert + * @return encoded base64 representation of source. + */ + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @return encoded base64 representation of source. + */ + public static String encodeBytes(byte[] source, int off, int len) { + final int len43 = len * 4 / 3; + + byte[] outBuff = new byte[len43 + ((len % 3) > 0 ? 4 : 0)]; + int d = 0; + int e = 0; + int len2 = len - 2; + + for (; d < len2; d += 3, e += 4) + encode3to4(source, d + off, 3, outBuff, e); + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e); + e += 4; + } + + try { + return new String(outBuff, 0, e, CHARSET_UTF8); + } catch (UnsupportedEncodingException uue) { + return new String(outBuff, 0, e); + } + } + + /** + * Decodes four bytes from array <var>source</var> and writes the resulting + * bytes (up to three of them) to <var>destination</var>. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying <var>srcOffset</var> and <var>destOffset</var>. This method + * does not check to make sure your arrays are large enough to accommodate + * <var>srcOffset</var> + 4 for the <var>source</var> array or + * <var>destOffset</var> + 3 for the <var>destination</var> array. This + * method returns the actual number of bytes that were converted from the + * Base64 encoding. + * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @return the number of decoded bytes converted + */ + private static int decode4to3(byte[] source, int srcOffset, + byte[] destination, int destOffset) { + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) + | ((DEC[source[srcOffset + 1]] & 0xFF) << 12); + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; + } + + // Example: DkL= + else if (source[srcOffset + 3] == EQUALS_SIGN) { + int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) + | ((DEC[source[srcOffset + 1]] & 0xFF) << 12) + | ((DEC[source[srcOffset + 2]] & 0xFF) << 6); + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; + } + + // Example: DkLE + else { + int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) + | ((DEC[source[srcOffset + 1]] & 0xFF) << 12) + | ((DEC[source[srcOffset + 2]] & 0xFF) << 6) + | ((DEC[source[srcOffset + 3]] & 0xFF)); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + + return 3; + } + } + + /** + * Low-level decoding ASCII characters from a byte array. + * + * @param source + * The Base64 encoded data + * @param off + * The offset of where to begin decoding + * @param len + * The length of characters to decode + * @return decoded data + * @throws IllegalArgumentException + * the input is not a valid Base64 sequence. + */ + public static byte[] decode(byte[] source, int off, int len) { + byte[] outBuff = new byte[len * 3 / 4]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + + for (int i = off; i < off + len; i++) { + byte sbiCrop = (byte) (source[i] & 0x7f); + byte sbiDecode = DEC[sbiCrop]; + + if (EQUALS_SIGN_DEC <= sbiDecode) { + b4[b4Posn++] = sbiCrop; + if (b4Posn > 3) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if (sbiCrop == EQUALS_SIGN) + break; + } + + } else if (sbiDecode != WHITE_SPACE_DEC) + throw new IllegalArgumentException(MessageFormat.format( + "Bad Base64 input character at {0} : {1} (decimal)", i, + source[i] & 0xff)); + } + + if (outBuff.length == outBuffPosn) + return outBuff; + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } + + /** + * Decodes data from Base64 notation. + * + * @param s + * the string to decode + * @return the decoded data + */ + public static byte[] decode(String s) { + byte[] bytes; + try { + bytes = s.getBytes(CHARSET_UTF8); + } catch (UnsupportedEncodingException uee) { + bytes = s.getBytes(); + } + return decode(bytes, 0, bytes.length); + } +} diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/EncodingUtils.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/EncodingUtils.java index 6f430977..21c08597 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/EncodingUtils.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/EncodingUtils.java @@ -14,8 +14,6 @@ import static org.eclipse.egit.github.core.client.IGitHubConstants.CHARSET_UTF8; import java.io.UnsupportedEncodingException; -import org.apache.commons.codec.binary.Base64; - /** * Encoding utilities */ @@ -28,11 +26,7 @@ public abstract class EncodingUtils { * @return byte array */ public static final byte[] fromBase64(final String content) { - try { - return Base64.decodeBase64(content.getBytes(CHARSET_UTF8)); - } catch (UnsupportedEncodingException e) { - return Base64.decodeBase64(content.getBytes()); - } + return Base64.decode(content); } /** @@ -41,7 +35,23 @@ public abstract class EncodingUtils { * @param content * @return byte array */ - public static final byte[] toBase64(final byte[] content) { - return Base64.encodeBase64(content); + public static final String toBase64(final byte[] content) { + return Base64.encodeBytes(content); + } + + /** + * Base64 encode given byte array + * + * @param content + * @return byte array + */ + public static final String toBase64(final String content) { + byte[] bytes; + try { + bytes = content.getBytes(CHARSET_UTF8); + } catch (UnsupportedEncodingException e) { + bytes = content.getBytes(); + } + return toBase64(bytes); } } diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/MultiPartUtils.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/MultiPartUtils.java new file mode 100644 index 00000000..875c6e09 --- /dev/null +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/MultiPartUtils.java @@ -0,0 +1,93 @@ +/****************************************************************************** + * Copyright (c) 2011 GitHub Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Kevin Sawicki (GitHub Inc.) - initial API and implementation + *****************************************************************************/ +package org.eclipse.egit.github.core.util; + +import static org.eclipse.egit.github.core.client.IGitHubConstants.CHARSET_UTF8; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Utilities for writing multiple HTTP requests + */ +public class MultiPartUtils { + + /** + * Post parts to URL + * + * @param url + * @param parts + * @return connection that was posted to + * @throws IOException + */ + public static HttpURLConnection post(String url, Map<String, Object> parts) + throws IOException { + HttpURLConnection post = (HttpURLConnection) new URL(url) + .openConnection(); + post.setRequestMethod("POST"); //$NON-NLS-1$ + return post(post, parts); + } + + /** + * Post parts to connection + * + * @param post + * @param parts + * @return connection that was posted to + * @throws IOException + */ + public static HttpURLConnection post(HttpURLConnection post, + Map<String, Object> parts) throws IOException { + String boundary = "00content0boundary00"; //$NON-NLS-1$ + post.setDoOutput(true); + post.setRequestProperty("Content-Type", //$NON-NLS-1$ + "multipart/form-data; boundary=" + boundary); //$NON-NLS-1$ + BufferedOutputStream output = new BufferedOutputStream( + post.getOutputStream()); + byte[] buffer = new byte[8192]; + byte[] boundarySeparator = ("--" + boundary + "\r\n") //$NON-NLS-1$ //$NON-NLS-2$ + .getBytes(CHARSET_UTF8); + byte[] newline = "\r\n".getBytes(CHARSET_UTF8); //$NON-NLS-1$ + try { + for (Entry<String, Object> part : parts.entrySet()) { + output.write(boundarySeparator); + StringBuilder partBuffer = new StringBuilder( + "Content-Disposition: "); //$NON-NLS-1$ + partBuffer.append("form-data; name=\""); //$NON-NLS-1$ + partBuffer.append(part.getKey()); + partBuffer.append('"'); + output.write(partBuffer.toString().getBytes(CHARSET_UTF8)); + output.write(newline); + output.write(newline); + final Object value = part.getValue(); + if (value instanceof InputStream) { + InputStream input = (InputStream) value; + int read; + while ((read = input.read(buffer)) != -1) + output.write(buffer, 0, read); + input.close(); + } else + output.write(part.getValue().toString() + .getBytes(CHARSET_UTF8)); + output.write(newline); + } + output.write(("--" + boundary + "--\r\n").getBytes(CHARSET_UTF8)); //$NON-NLS-1$ //$NON-NLS-2$ + } finally { + output.close(); + } + return post; + } +} diff --git a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/UrlUtils.java b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/UrlUtils.java index 62e2f9e0..4b24de92 100644 --- a/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/UrlUtils.java +++ b/org.eclipse.egit.github.core/src/org/eclipse/egit/github/core/util/UrlUtils.java @@ -10,13 +10,17 @@ *****************************************************************************/ package org.eclipse.egit.github.core.util; +import static org.eclipse.egit.github.core.client.IGitHubConstants.CHARSET_ISO_8859_1; import static org.eclipse.egit.github.core.client.IGitHubConstants.HOST_DEFAULT; import static org.eclipse.egit.github.core.client.IGitHubConstants.SUFFIX_GIT; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.Map; +import java.util.Map.Entry; -import org.apache.http.protocol.HTTP; import org.eclipse.egit.github.core.IRepositoryIdProvider; /** @@ -25,20 +29,6 @@ import org.eclipse.egit.github.core.IRepositoryIdProvider; public abstract class UrlUtils { /** - * Encode given url - * - * @param url - * @return encoded url - */ - public static String encode(String url) { - try { - return URLEncoder.encode(url, HTTP.DEFAULT_CONTENT_CHARSET); - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException(e); - } - } - - /** * Create SSH URL used for repository remote configs * * @param repository @@ -108,4 +98,85 @@ public abstract class UrlUtils { IRepositoryIdProvider repository, String host) { return "git://" + host + "/" + repository.generateId() + SUFFIX_GIT; //$NON-NLS-1$ //$NON-NLS-2$ } + + /** + * URL-encode value using 'ISO-8859-1' character set + * + * @param value + * @return encoded value + */ + public static String encode(final String value) { + try { + return URLEncoder.encode(value, CHARSET_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * URL-decode value using 'ISO-8859-1' character set + * + * @param value + * @return encoded value + */ + public static String decode(final String value) { + try { + return URLDecoder.decode(value, CHARSET_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Add encoded parameter to URI + * + * @param name + * @param value + * @param uri + */ + public static void addParam(final String name, final String value, + final StringBuilder uri) { + if (uri.length() > 0) + uri.append('&'); + uri.append(encode(name)).append('='); + if (value != null) + uri.append(encode(value)); + } + + /** + * Add request parameters to URI + * + * @param params + * @param uri + */ + public static void addParams(final Map<String, String> params, + final StringBuilder uri) { + if (params == null || params.isEmpty()) + return; + for (Entry<String, String> param : params.entrySet()) + addParam(param.getKey(), param.getValue(), uri); + } + + /** + * Get parameter value with name + * + * @param uri + * @param name + * @return value or null if not found in URI query + */ + public static String getParam(final URI uri, final String name) { + final String query = uri.getRawQuery(); + if (query == null || query.length() == 0) + return null; + final String[] params = query.split("&"); //$NON-NLS-1$ + for (String param : params) { + final String[] parts = param.split("="); //$NON-NLS-1$ + if (parts.length != 2) + continue; + if (!name.equals(parts[0])) + continue; + return decode(parts[1]); + } + return null; + } } |