/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package liveplugin.toolwindow.addplugin.git.jetbrains.plugins.github.util;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vcs.CalledInAwt;
import com.intellij.util.ThrowableConvertor;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.security.validator.ValidatorException;
import java.io.IOException;
/**
* Provides various methods to work with SSL certificate protected HTTPS connections.
*
* @author Kirill Likhodedov
*/
public class GithubSslSupport {
private static GithubSslSupport instance;
public static GithubSslSupport getInstance() {
if (instance == null) {
instance = new GithubSslSupport();
}
return instance;
}
/**
* Tries to execute the {@link org.apache.commons.httpclient.HttpMethod} and captures the {@link sun.security.validator.ValidatorException exception} which is thrown if user connects
* to an HTTPS server with a non-trusted (probably, self-signed) SSL certificate. In which case proposes to cancel the connection
* or to proceed without certificate check.
*
* @param methodCreator a function to create the HttpMethod. This is required instead of just {@link org.apache.commons.httpclient.HttpMethod} instance, because the
* implementation requires the HttpMethod to be recreated in certain circumstances.
* @return the HttpMethod instance which was actually executed
* and which can be {@link org.apache.commons.httpclient.HttpMethod#getResponseBodyAsString() asked for the response}.
* @throws java.io.IOException in case of other errors or if user declines the proposal of non-trusted connection.
*/
@NotNull
public HttpMethod executeSelfSignedCertificateAwareRequest(@NotNull HttpClient client,
@NotNull String uri,
@NotNull ThrowableConvertor<String, HttpMethod, IOException> methodCreator)
throws IOException {
HttpMethod method = methodCreator.convert(uri);
try {
client.executeMethod(method);
return method;
}
catch (IOException e) {
HttpMethod m = handleCertificateExceptionAndRetry(e, method.getURI().getHost(), client, method.getURI(), methodCreator);
if (m == null) {
throw e;
}
return m;
}
}
@Nullable
private static HttpMethod handleCertificateExceptionAndRetry(@NotNull IOException e,
@NotNull String host,
@NotNull HttpClient client,
@NotNull URI uri,
@NotNull ThrowableConvertor<String, HttpMethod, IOException> methodCreator)
throws IOException {
if (!isCertificateException(e)) {
throw e;
}
if (isTrusted(host)) {
// creating a special configuration that allows connections to non-trusted HTTPS hosts
// see the javadoc to EasySSLProtocolSocketFactory for details
Protocol easyHttps = new Protocol("https", (ProtocolSocketFactory)new EasySSLProtocolSocketFactory(), 443);
HostConfiguration hc = new HostConfiguration();
hc.setHost(host, 443, easyHttps);
String relativeUri = new URI(uri.getPathQuery(), false).getURI();
// it is important to use relative URI here, otherwise our custom protocol won't work.
// we have to recreate the method, because HttpMethod#setUri won't overwrite the host,
// and changing host by hands (HttpMethodBase#setHostConfiguration) is deprecated.
HttpMethod method = methodCreator.convert(relativeUri);
client.executeMethod(hc, method);
return method;
}
throw e;
}
public static boolean isCertificateException(IOException e) {
return e.getCause() instanceof ValidatorException;
}
private static boolean isTrusted(@NotNull String host) {
return GithubSettings.getInstance().getTrustedHosts().contains(host.toLowerCase());
}
private static void saveToTrusted(@NotNull String host) {
GithubSettings.getInstance().addTrustedHost(host.toLowerCase());
}
@CalledInAwt
public boolean askIfShouldProceed(final String url) {
String host = GithubUrlUtil.getHostFromUrl(url);
int choice = Messages.showYesNoDialog("The security certificate of " + host + " is not trusted. Do you want to proceed anyway?",
"Not Trusted Certificate", "Proceed anyway", "No, I don't trust", Messages.getErrorIcon());
boolean trust = (choice == Messages.YES);
if (trust) {
saveToTrusted(host);
}
return trust;
}
}