/*
 * 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 org.jetbrains.plugins.github.util;

import com.intellij.openapi.components.ServiceManager;
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 {

  public static GithubSslSupport getInstance() {
    return ServiceManager.getService(GithubSslSupport.class);
  }

  /**
   * Tries to execute the {@link HttpMethod} and captures the {@link 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 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 HttpMethod#getResponseBodyAsString() asked for the response}.
   * @throws 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);
  }

  private static void saveToTrusted(@NotNull String host) {
    GithubSettings.getInstance().addTrustedHost(host);
  }

  @CalledInAwt
  public boolean askIfShouldProceed(final String url) {
    String host = GithubUrlUtil.getHostFromUrl(url);
    if (host == null) {
      GithubUtil.LOG.warn("Bad SSL host: " + url);
      return false;
    }

    final String BACK_TO_SAFETY = "No, I don't trust";
    final String TRUST = "Proceed anyway";
    //int choice = Messages.showDialog("The security certificate of " + host + " is not trusted. Do you want to proceed anyway?",
    //                               "Not Trusted Certificate", new String[] { BACK_TO_SAFETY, TRUST }, 0, Messages.getErrorIcon());
    int choice = Messages.showIdeaMessageDialog(null, "The security certificate of " + host + " is not trusted. Do you want to proceed anyway?",
                                     "Not Trusted Certificate", new String[] { BACK_TO_SAFETY, TRUST }, 0, Messages.getErrorIcon(), null);
    boolean trust = (choice == 1);
    if (trust) {
      saveToTrusted(host);
    }
    return trust;
  }

}

@@ -2298,24 +2298,16 @@
 ted%0A   *
-        
  and whi
@@ -2385,16 +2385,16 @@
 ponse%7D.%0A
+
    * @th
@@ -2591,32 +2591,93 @@
 tpClient client,
+%0A                                                            
  @NotNull String
@@ -2823,65 +2823,8 @@
 or)%0A
-                                                         
     
@@ -2840,24 +2840,24 @@
 Exception %7B%0A
+
     HttpMeth
@@ -3297,16 +3297,79 @@
 ption e,
+%0A                                                              
  @NotNul
@@ -3473,16 +3473,79 @@
  client,
+%0A                                                              
  @NotNul
@@ -3698,67 +3698,8 @@
 or)%0A
-                                                           
     
@@ -3715,24 +3715,24 @@
 Exception %7B%0A
+
     if (!isC
@@ -5286,108 +5286,8 @@
     
-final String BACK_TO_SAFETY = %22No, I don't trust%22;%0A    final String TRUST = %22Proceed anyway%22;%0A    //
 int 
@@ -5308,16 +5308,21 @@
 ges.show
+YesNo
 Dialog(%22
@@ -5415,18 +5415,18 @@
 ?%22,%0A    
-//
+  
         
@@ -5440,32 +5440,37 @@
                 
+     
 %22Not Trusted Cer
@@ -5484,205 +5484,10 @@
 e%22, 
-new String%5B%5D %7B BACK_TO_SAFETY, TRUST %7D, 0, Messages.getErrorIcon());%0A    int choice = Messages.showIdeaMessageDialog(null, %22The security certificate of %22 + host + %22 is not trusted. Do you want to p
+%22P
 roce
@@ -5499,117 +5499,30 @@
 yway
-?
 %22,
-%0A                                     %22Not T
+ %22No, I don't t
 rust
-ed Certificate%22, new String%5B%5D %7B BACK_TO_SAFETY, TRUST %7D, 0
+%22
 , Me
@@ -5546,14 +5546,8 @@
 on()
-, null
 );%0A 
@@ -5576,17 +5576,28 @@
 oice == 
-1
+Messages.YES
 );%0A    i

use standard YesNoDialog