All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.subversion.client.SvnClientExceptionHandler Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.netbeans.modules.subversion.client;

import org.netbeans.modules.subversion.util.NotifyHtmlPanel;
import java.awt.Dialog;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.swing.JButton;
import org.netbeans.modules.subversion.Subversion;
import org.netbeans.modules.subversion.client.SvnClientFactory.ConnectionType;
import org.netbeans.modules.subversion.SvnModuleConfig;
import org.netbeans.modules.subversion.WorkingCopyAttributesCache;
import org.netbeans.modules.subversion.client.cli.CommandlineClient;
import org.netbeans.modules.subversion.config.CertificateFile;
import org.netbeans.modules.subversion.ui.repository.Repository;
import org.netbeans.modules.subversion.ui.repository.RepositoryConnection;
import org.netbeans.modules.subversion.ui.wcadmin.UpgradeAction;
import org.netbeans.modules.versioning.util.FileUtils;
import org.netbeans.modules.subversion.util.SvnUtils;
import org.netbeans.modules.versioning.util.KeyringSupport;
import org.netbeans.modules.versioning.util.Utils;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.Mnemonics;
import org.openide.util.HelpCtx;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.NetworkSettings;
import org.openide.util.actions.SystemAction;
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;
import org.tigris.subversion.svnclientadapter.SVNClientException;
import org.tigris.subversion.svnclientadapter.SVNRevision;
import org.tigris.subversion.svnclientadapter.SVNUrl;

/**
 *
 * @author Tomas Stupka
 */
public class SvnClientExceptionHandler {
    private final ISVNClientAdapter adapter;
    private final SvnClient client;
    private final SvnClientDescriptor desc;    
    private final int handledExceptions;
    
    private static final String NEWLINE = System.getProperty("line.separator"); // NOI18N
    private static final String CHARSET_NAME = "ASCII7";                        // NOI18N
    private String methodName;
    private final ConnectionType connectionType;
    
    private class CertificateFailure {
        int mask;
        String error;
        String message;
        CertificateFailure(int mask, String error, String message) {
            this.mask = mask;
            this.error = error;
            this.message = message;
        }
    };
   
    private final CertificateFailure[] failures = new CertificateFailure[] {       
        new CertificateFailure (1, "certificate is not yet valid" ,                 NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_CertFailureNotYetValid")),       // NOI18N
        new CertificateFailure (2, "certificate has expired" ,                      NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_CertFailureHasExpired")),        // NOI18N
        new CertificateFailure (4, "certificate issued for a different hostname" ,  NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_CertFailureWrongHostname")),     // NOI18N
        new CertificateFailure (8, "issuer is not trusted" ,                        NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_CertFailureNotTrusted"))         // NOI18N
    };
    
    public static final int EX_UNKNOWN = 0;
    public static final int EX_ACTION_CANCELED_BY_USER = 2;
    public static final int EX_AUTHENTICATION = 4;
    public static final int EX_NO_CERTIFICATE = 8;
    public static final int EX_WRONG_URL = 16;
    public static final int EX_NO_HOST_CONNECTION = 32;
    public static final int EX_UNVERSIONED_RESOURCE = 64;
    public static final int EX_WRONG_URL_IN_REVISION = 128;
    public static final int EX_URL_NON_EXISTENT = 256;
    public static final int EX_HTTP_405 = 512;
    public static final int EX_IS_ALREADY_WC = 1024;
    public static final int EX_CLOSED_CONNECTION = 2048;
    public static final int EX_COMMIT_FAILED = 4096;
    public static final int EX_FILE_ALREADY_EXISTS = 8192;
    public static final int EX_IS_OUT_OF_DATE = 16384;            
    public static final int EX_NO_SVN_CLIENT = 32768;            
    public static final int EX_HTTP_FORBIDDEN = 65536;      
    public static final int EX_SSL_NEGOTIATION_FAILED = 131072;
          
  
    public static final int EX_HANDLED_EXCEPTIONS = EX_AUTHENTICATION | EX_NO_CERTIFICATE | EX_NO_HOST_CONNECTION | EX_SSL_NEGOTIATION_FAILED | EX_HTTP_FORBIDDEN;
    public static final int EX_DEFAULT_HANDLED_EXCEPTIONS = EX_HANDLED_EXCEPTIONS;
    
    private final SVNClientException exception;
    private final int exceptionMask;
            
    static final String ACTION_CANCELED_BY_USER = org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_ActionCanceledByUser");
    
    public SvnClientExceptionHandler(SVNClientException exception, ISVNClientAdapter adapter, SvnClient client, SvnClientDescriptor desc, int handledExceptions, SvnClientFactory.ConnectionType connectionType) {
        this.exception = exception;                
        this.adapter = adapter;
        this.client = client;
        this.desc = desc;
        this.handledExceptions = handledExceptions;
        exceptionMask = getMask(exception.getMessage());
        this.connectionType = connectionType;
    }      

    public boolean handleException() throws SVNClientException {
        if(exceptionMask != EX_UNKNOWN) {
            if( (handledExceptions & exceptionMask & EX_NO_HOST_CONNECTION) == exceptionMask) {
                return handleRepositoryConnectError();
            } if( (handledExceptions & exceptionMask & EX_NO_CERTIFICATE) == exceptionMask) {                        
                return handleNoCertificateError();
            } if( (handledExceptions &  exceptionMask & EX_AUTHENTICATION) == exceptionMask) {
                return handleRepositoryConnectError();
            } if( (handledExceptions &  exceptionMask & EX_SSL_NEGOTIATION_FAILED) == exceptionMask) {
                return handleRepositoryConnectError();
            } if( (handledExceptions &  exceptionMask & EX_HTTP_FORBIDDEN) == exceptionMask) {
                return handleRepositoryConnectError();
            }
        }
        throw getException();
    }
    
    void setMethod (String methodName) {
        this.methodName = methodName;
    }

    private boolean handleRepositoryConnectError() throws SVNClientException {
        SVNUrl url = getRemoteHostUrl(); // try to get the repository url from the svnclientdescriptor
        if (url == null) {
            // god knows why this can happen
            return false;
        }

        if (Thread.interrupted()) {
            Subversion.LOG.log(Level.FINE, "SvnClientExceptionHandler.handleRepositoryConnectError(): canceled"); //NOI18N
            return false;
        }
        Repository repository = new Repository(Repository.FLAG_SHOW_PROXY, org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_ConnectionParameters"));  // NOI18N
        repository.selectUrl(url, true);

        JButton retryButton = new JButton(org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Action_Retry"));           // NOI18N
        String title = ((exceptionMask & EX_NO_HOST_CONNECTION) == exceptionMask) ?
                            org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_CouldNotConnect") :
                            org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_AuthFailed");
        Object option = repository.show(title, new HelpCtx(this.getClass()), new Object[] {retryButton, org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Action_Cancel")},    // NOI18N
                retryButton);

        boolean ret = (option == retryButton);
        if(ret) {
            RepositoryConnection rc = repository.getSelectedRC();
            String username = rc.getUsername();
            char[] password = rc.getPassword();

            adapter.setUsername(username);
            if (connectionType != ConnectionType.javahl) {
                adapter.setPassword(password != null ? new String(password) : ""); //NOI18N
            }
            SvnModuleConfig.getDefault().insertRecentUrl(rc);
        }
        return ret;
    }

    @NbBundle.Messages("CTL_Action_Cancel=Cancel")
    public static boolean handleAuth (SVNUrl url) {
        Repository repository = new Repository(Repository.FLAG_SHOW_PROXY, org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_ConnectionParameters"));  // NOI18N
        repository.selectUrl(url, true);

        JButton retryButton = new JButton(org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Action_Retry"));           // NOI18N
        String title = org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_AuthFailed");
        Object option = repository.show(
                title, 
                new HelpCtx("org.netbeans.modules.subversion.client.SvnClientExceptionHandler"), //NOI18N
                new Object[] { retryButton,
                    Bundle.CTL_Action_Cancel()
                }, retryButton);

        boolean ret = (option == retryButton);
        if(ret) {
            SvnModuleConfig.getDefault().insertRecentUrl(repository.getSelectedRC());
        }
        return ret;
    }

    private boolean handleNoCertificateError() throws SVNClientException {
        
        SVNUrl url = getSVNUrl(); // get the remote host url
        String realmString = url.getProtocol() + "://" + url.getHost() + ":" + url.getPort(); // NOI18N
        String hostString = SvnUtils.ripUserFromHost(url.getHost());                                
        
        // copy the certificate if it already exists        
        File certFile = CertificateFile.getSystemCertFile(realmString);
        File nbCertFile = CertificateFile.getNBCertFile(realmString);
        if( !nbCertFile.exists() &&  certFile.exists() ) {            
            try {
                FileUtils.copyFile(certFile, CertificateFile.getNBCertFile(realmString));
            } catch (IOException ex) {
                throw new SVNClientException(ex);
            }
            return true;
        }

        // otherwise try to retrieve the certificate from the server ...                                             
        SSLSocket socket;
        try {
            socket = getSSLSocket(hostString, url.getPort(), null, url);
        } catch (Exception e) {
            throw new SVNClientException(e);
        }
        if(socket == null) {
            return false;
        }

        X509Certificate cert = null;
        java.security.cert.Certificate[] serverCerts = null;
        try {
            serverCerts = socket.getSession().getPeerCertificates();
        } catch (SSLPeerUnverifiedException ex) {
            throw new SVNClientException(ex);
        }
        for (Certificate serverCert : serverCerts) {
            if (serverCert instanceof X509Certificate) {
                cert = (X509Certificate) serverCert;
                try {
                    cert.checkValidity();
                } catch (CertificateExpiredException ex) {
                    continue; // try to get the next one
                } catch (CertificateNotYetValidException ex) {
                    continue; // try to get the next one
                }
                break;
            }
        }

        AcceptCertificatePanel acceptCertificatePanel = new AcceptCertificatePanel();
        acceptCertificatePanel.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Error_CertFailed")); // NOI18N
        acceptCertificatePanel.certificatePane.setText(getCertMessage(cert, hostString));
        DialogDescriptor dialogDescriptor = new DialogDescriptor(acceptCertificatePanel, org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Error_CertFailed")); // NOI18N
        dialogDescriptor.setHelpCtx(new HelpCtx("org.netbeans.modules.subversion.serverCertificateVerification"));
        JButton permanentlyButton = new JButton(org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Cert_AcceptPermanently")); // NOI18N
        JButton temporarilyButton = new JButton(org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Cert_AcceptTemp")); // NOI18N
        JButton rejectButton = new JButton(org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_Cert_Reject")); // NOI18N
        dialogDescriptor.setOptions(new Object[] {permanentlyButton, temporarilyButton, rejectButton}); 

        showDialog(dialogDescriptor);

        if(dialogDescriptor.getValue()!=permanentlyButton && dialogDescriptor.getValue()!=temporarilyButton) {                
            return false;
        }

        try {
            boolean temporarily = dialogDescriptor.getValue() == temporarilyButton;
            CertificateFile cf = new CertificateFile(cert, "https://" + hostString + ":" + url.getPort(), getFailuresMask(), temporarily); // NOI18N
            cf.store();
        } catch (CertificateEncodingException ex) {
            throw new SVNClientException(ex);
        } catch (IOException ex) {
            throw new SVNClientException(ex);
        }
            
        return true;                
    }

    private SVNUrl getSVNUrl() {
        String realmString = getRealmFromException(); 
        SVNUrl url = null; 
        if(realmString != null) {
            try {
                url = new SVNUrl(realmString);
            } catch (MalformedURLException e) {
                // something went wrong. 
                // ignore and try to fallback on the url from client
                Subversion.LOG.log(Level.INFO, e.getMessage(), e);                
            }    
        }                 
        if(url == null) {
            // fallback to the best we have
            url = client.getSvnUrl();
        }        
        return url;
    }
    private String getRealmFromException() {        
        String exceptionMessage = exception.getMessage().toLowerCase();             
        String[] errorMessages = new String[] {
            "host not found (", 
            "could not connect to server (", 
            "could not resolve hostname (", 
            "issuer is not trusted (",
            "authorization failed ("
        };        
        for(String errorMessage : errorMessages) {
            int idxL = exceptionMessage.indexOf(errorMessage);
            if(idxL < 0) {
                continue;
            }
            int idxR = exceptionMessage.indexOf(")", idxL + errorMessage.length());
            if(idxR < 0) {
                continue;
            }
            return exceptionMessage.substring(idxL + errorMessage.length(), idxR);                                        
        }
        return null;
    }  
    
    private SSLSocket getSSLSocket(String host, int port, String[] protocols, SVNUrl url) throws Exception {
        TrustManager[] trust = new TrustManager[] {
            new X509TrustManager() {
            @Override
                public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
            @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) { }
            @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) { }
            }
        };
        
        URI uri = null;
        try {
            uri = new URI(url.toString());
        } catch (URISyntaxException ex) {
            Subversion.LOG.log(Level.INFO, null, ex);
        }
       
        String proxyHost = uri == null ? null : NetworkSettings.getProxyHost(uri);
        String proxyPort = uri == null ? null : NetworkSettings.getProxyPort(uri);

        // now this is the messy part ...
        Socket proxySocket = new Socket(java.net.Proxy.NO_PROXY);
        if(proxyHost == null || proxyHost.length() == 0) {                                           
            proxySocket.connect(new InetSocketAddress(host, port));
        } else {
            boolean directWorks = false;
            try {
                proxySocket.connect(new InetSocketAddress(host, port));
                directWorks = true;
            } catch (IOException e) {
                // do nothing
                Subversion.LOG.log(Level.FINE, null, e);
            }
            if(!directWorks) {
                proxySocket = new Socket(java.net.Proxy.NO_PROXY); // reusing sockets seems to cause problems - see #138916
                proxySocket.connect(new InetSocketAddress(proxyHost, Integer.valueOf(proxyPort)));
                String username = NetworkSettings.getAuthenticationUsername(uri);
                String password = null;
                if (username != null) {
                    char[] pwd = KeyringSupport.read(NetworkSettings.getKeyForAuthenticationPassword(uri), null);
                    password = pwd == null ? "" : new String(pwd); //NOI18N
                }
                connectProxy(proxySocket, host, port, proxyHost, proxyPort, username, password);
            } 
        }
                        
        SSLContext context = SSLContext.getInstance("SSL");                     // NOI18N
        context.init(getKeyManagers(), trust, null);
        SSLSocketFactory factory = context.getSocketFactory();
        SSLSocket socket = (SSLSocket) factory.createSocket(proxySocket, host, port, true);
        if (protocols != null) {
            socket.setEnabledProtocols(protocols);
        }
        try {
            socket.startHandshake();
        } catch (SSLException ex) {
            if (protocols == null && isBadRecordMac(ex.getMessage())) {
                return getSSLSocket(host, port, new String[] {"SSLv3", "SSLv2Hello"}, url); //NOI18N
            } else {
                throw ex;
            }
        }
        return socket;
    }
    
    private KeyManager[] getKeyManagers() {        
        try {
            SVNUrl url = getRemoteHostUrl();
            RepositoryConnection rc = SvnModuleConfig.getDefault().getRepositoryConnection(url.toString());
            if(rc == null) {
                return null;
            }
            String certFile = rc.getCertFile();
            if(certFile == null || certFile.trim().equals("")) {                            // NOI18N
                return null;
            }               
            char[] certPasswordChars = rc.getCertPassword();
            
            KeyStore ks = KeyStore.getInstance("pkcs12");                                   // NOI18N            
            FileInputStream fis = new FileInputStream(certFile);
            try {
                ks.load(fis, certPasswordChars);
            } finally {
                fis.close();
            }
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, certPasswordChars);
            return kmf.getKeyManagers();
            
        } catch(IOException ex) {
            Subversion.LOG.log(Level.SEVERE, null, ex);
        } catch (GeneralSecurityException ex) {
            Subversion.LOG.log(Level.SEVERE, null, ex);
        }                                       
        return null;
    }

    private SVNUrl getRemoteHostUrl() {
        SVNUrl url = desc != null ? desc.getSvnUrl() : null;
        if (url == null) {
            // huh ??? - try to fallback to the url given by the error msg.
            // unfortunatelly - this musn't be the repo url but only the the remote host url
            url = getSVNUrl();
        }
        return url;
    }

    private void connectProxy(Socket proxy, String host, int port, String proxyHost, String proxyPort, String userName, String password) throws IOException {
      StringBuilder sb = new StringBuilder("CONNECT ").append(host).append(":").append(port).append(" HTTP/1.0\r\n") //NOI18N
              .append("Connection: Keep-Alive\r\n");                    //NOI18N
      if (userName != null && password != null && userName.length() > 0) {
          Subversion.LOG.info("connectProxy: adding proxy authorization field"); //NOI18N
          sb.append("Proxy-Authorization: Basic ").append(Base64.getEncoder().encodeToString((userName + ":" + password).getBytes())).append("\r\n"); //NOI18N
      }
      String connectString = sb.append("\r\n").toString();
      byte connectBytes[];
      try {
         connectBytes = connectString.getBytes(CHARSET_NAME);
      } catch (UnsupportedEncodingException ignored) {
         connectBytes = connectString.getBytes();
      }
      
      OutputStream out = proxy.getOutputStream();
      out.write(connectBytes);
      out.flush();

      byte reply[] = new byte[200];
      int replyLen = 0;
      int newlinesSeen = 0;
      boolean headerDone = false;
      InputStream in = proxy.getInputStream();
      
      while (newlinesSeen < 2) {
         byte b = (byte) in.read();
         if (b < 0) {
            throw new IOException("Unexpected EOF from proxy");                 // NOI18N
         }
         if (b == '\n') {
            headerDone = true;
            ++newlinesSeen;
         } else if (b != '\r') {
            newlinesSeen = 0;
            if (!headerDone && replyLen < reply.length) {
               reply[replyLen++] = b;
            }
         }
      }

      String ret;
      try {
        ret = new String(reply, 0, replyLen, CHARSET_NAME);
      } catch (UnsupportedEncodingException ignored) {
        ret = new String(reply, 0, replyLen);
      }
        if (!isOKresponse(ret.toLowerCase())) {
            throw new IOException("Unable to connect through proxy "            // NOI18N
                                 + proxyHost + ":" + proxyPort                  // NOI18N
                                 + ".  Proxy returns \"" + ret + "\"");         // NOI18N
        }
    }    
    
    private boolean isOKresponse(String ret) {
        return ret.startsWith("http/1.1 200") || ret.startsWith("http/1.0 200");// NOI18N
    }    
    
    private void showDialog(DialogDescriptor dialogDescriptor) {
        dialogDescriptor.setModal(true);
        dialogDescriptor.setValid(false);     

        Dialog dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);        
        dialog.setVisible(true);
    }     

    private String getCertMessage(X509Certificate cert, String host) { 
        CertificateFailure[] certFailures = getCertFailures();
        Object[] param = new Object[7];
        param[0] = host;
        param[1] = cert.getSubjectDN().getName();
        param[2] = cert.getNotBefore();
        param[3] = cert.getNotAfter();
        param[4] = cert.getIssuerDN().getName();
        param[5] = getFingerprint(cert, "SHA1");      // NOI18N
        param[6] = getFingerprint(cert, "MD5");       // NOI18N

        String message = NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_BadCertificate", param); // NOI18N
        for (CertificateFailure certFailure : certFailures) {
            message = certFailure.message + message;
        }
        return message;
    }

    private CertificateFailure[] getCertFailures() {
        List ret = new ArrayList();
        String exceptionMessage = getException().getMessage();
        for (CertificateFailure failure : failures) {
            if (exceptionMessage.indexOf(failure.error) > -1) {
                ret.add(failure);
            }
        }
        return ret.toArray(new CertificateFailure[0]);
    }
   
    private int getFailuresMask() {
        CertificateFailure[] certFailures = getCertFailures();
        if(certFailures.length == 0) {
            return 15; // something went wrong, 15 should work for everything
        }
        int mask = 0;
        for (CertificateFailure certFailure : certFailures) {
            mask |= certFailure.mask;
        }
        return mask;
    }
    
    private String getFingerprint(X509Certificate cert, String alg) {
        try {
            byte[] encoded = cert.getEncoded();            
            return Utils.getHash(alg, encoded);
        } catch (CertificateEncodingException ex) {
            Subversion.LOG.log(Level.INFO, ex.getMessage(), ex); // should not happen
        } catch (NoSuchAlgorithmException ex) {
            Subversion.LOG.log(Level.INFO, ex.getMessage(), ex); // should not happen
        }                       
        return ""; // NOI18N
    }
    
    private SVNClientException getException() {
        return exception;
    }

    private static int getMask(String msg) {
        if(msg == null || msg.trim().equals("")) {
            return EX_UNKNOWN;
        }
        msg = msg.toLowerCase();        
        if(isAuthentication(msg)) {         
            return EX_AUTHENTICATION;
        } else if (isCancelledAction(msg)) {
            return EX_ACTION_CANCELED_BY_USER;
        } else if (isNoCertificate(msg)) {
            return EX_NO_CERTIFICATE;
        } else if (isWrongUrl(msg)) {
            return EX_WRONG_URL;
        } else if (isNoHostConnection(msg)) {
            return EX_NO_HOST_CONNECTION;
        } else if(isUnversionedResource(msg)) {
            return EX_UNVERSIONED_RESOURCE;
        } else if(isWrongURLInRevision(msg)) {
            return EX_WRONG_URL_IN_REVISION;
        } else if(isHTTP405(msg)) { 
            return EX_HTTP_405;
        } else if(isAlreadyAWorkingCopy(msg)) {
            return EX_IS_ALREADY_WC;
        } else if(isClosedConnection(msg)) {
            return EX_CLOSED_CONNECTION;
        } else if(isCommitFailed(msg)) {
            return EX_COMMIT_FAILED;
        } else if(isNoCliSvnClient(msg)) {
            return EX_NO_SVN_CLIENT;
        } else if(isHTTP403(msg)) {
            return EX_HTTP_FORBIDDEN;
        } else if(isSSLNegotiation(msg)) {
            return EX_SSL_NEGOTIATION_FAILED;
        }
        return EX_UNKNOWN;
    }
    
    public static boolean isCancelledAction(String msg) {
        return msg.equals(ACTION_CANCELED_BY_USER);
    }

    static boolean isOperationCancelled(String message) {
        message = message.toLowerCase();
        return message.indexOf("operation canceled") > -1
                || message.contains("closedchannelexception"); //NOI18N - canceling a command while in svnkit/sqljet throws ClosedChannelException, no CanceledEx
    }

    public static boolean isAuthentication(String msg) {
        msg = msg.toLowerCase();       
        return msg.indexOf("authentication error from server: username not found") > - 1 || // NOI18N
               msg.indexOf("authorization failed") > - 1 ||                                 // NOI18N
               msg.indexOf("authentication failed") > - 1 ||                                // NOI18N
               msg.indexOf("authentication cancelled") > - 1 ||                             // NOI18N
               msg.indexOf("authentication error from server: password incorrect") > -1 ||  // NOI18N
               msg.indexOf("can't get password") > - 1 ||                                   // NOI18N
               msg.contains("user canceled dialog") ||                                      // NOI18N
               msg.contains("mkactivity request failed on") ||                              // NOI18N
               msg.contains("could not authenticate to server") ||                          // NOI18N
               msg.contains("unable to connect to a repository") && msg.contains("undefined tunnel scheme") || //NOI18N
               msg.indexOf("can't get username or password") > - 1;                         // NOI18N
    }

    public static boolean isNoCertificate(String msg) {
        msg = msg.toLowerCase();       
        return msg.indexOf("server certificate verification failed") > -1 // NOI18N
                || msg.contains("server ssl certificate untrusted") //NOI18N
                || msg.contains("server ssl certificate verification failed"); //NOI18N
    }
    
    public static boolean isWrongUrl(String msg) {
//      javahl: org.tigris.subversion.javahl.ClientException: Bad URL passed to RA layer
//      svn: URL 'file:///data/subversion/dilino' non-existent in revision 88
        msg = msg.toLowerCase();
        return msg.indexOf("(not a valid url)") > - 1 ||                                      // NOI18N
               (msg.contains("svn:") && msg.contains("url") && msg.contains("non-existent in")) || //NOI18N
               (msg.indexOf("bad url passed to ra layer") > - 1 );
    }

    private static boolean isNoHostConnection(String msg) {
        msg = msg.toLowerCase();       
        return msg.indexOf("host not found") > -1 ||                                        // NOI18N
               msg.indexOf("could not connect to server") > -1 ||                           // NOI18N
               msg.contains("cannot connect to") && msg.contains("there was a problem while connecting to") || // NOI18N
               msg.indexOf("could not resolve hostname") > -1;                              // NOI18N
    }
    
    public static boolean isUnversionedResource(String msg) {
        msg = msg.toLowerCase();
        return msg.indexOf("(not a versioned resource)") > -1 ||                            // NOI18N
               msg.indexOf("is not a working copy") > -1 ||                                 //NOI18N
               msg.contains("some targets are not versioned") ||                            //NOI18N
               isNodeUnderVersionControl(msg) ||
               isNodeNotFound(msg);
    }
    
    public static boolean hasNoBaseRevision (String msg) {
        msg = msg.toLowerCase();
        return msg.contains("has no base revision until it is committed"); //NOI18N
    }

    public static boolean isTooOldClientForWC(String msg) {
        msg = msg.toLowerCase();
        return msg.indexOf("this client is too old") > -1;                                   // NOI18N
    }
    
    public static boolean isWrongURLInRevision(String msg) {        
        msg = msg.toLowerCase();
        if (isNoSuchRevision(msg)) {
            return true;
        }
        int idx = msg.indexOf("unable to find repository location for");                    // NOI18N
        if(idx > -1 && msg.indexOf("in revision", idx + 23) > -1) {                         // NOI18N
            return true;
        }
        idx = msg.indexOf("url");                                                           // NOI18N
        return idx > -1 && (msg.indexOf("non-existent in that revision", idx + 3) > -1      //NOI18N
                || msg.indexOf("non-existent in revision", idx + 3) > -1);                  //NOI18N
    }    

    public static boolean isNoSuchRevision (String msg) {
        msg = msg.toLowerCase();
        return msg.contains("no such revision"); //NOI18N
    }

    private static boolean isHTTP405(String msg) {
        return msg.indexOf("405") > -1;                                                     // NOI18N
    }

    public static boolean isHTTP403(String msg) {
        return msg.indexOf("403") > -1;                                                     // NOI18N
    }
    
    public static boolean isSSLNegotiation(String msg) {
        msg = msg.toLowerCase();
        return msg.indexOf("ssl negotiation failed: ssl error: sslv3 alert handshake failure") > -1;                                                     // NOI18N
    }

    public static boolean isReportOf200(String msg) {  
        msg = msg.toLowerCase();
        int idx = msg.indexOf("svn: report of");            // NOI18N
        if(idx < 0) {
            return false;
        }
        return msg.indexOf("200", idx + 13) > -1;           // NOI18N
    }
    
    public static boolean isSecureConnTruncated(String msg) {
        msg = msg.toLowerCase();
        return msg.indexOf("could not read chunk size: secure connection truncated") > -1;  // NOI18N
    }        

    public static boolean isFileNotFoundInRevision(String msg) {

//      javahl:
//      Unable to find repository location for 'file:///data/subversion/JavaApplication31/nbproject/project.xml' in revision 87

//      cli:
//      svn: File not found: revision 87, path '/JavaApplication31/src/javaapplication31/Main.java'

        msg = msg.toLowerCase();
        return msg.indexOf("file not found: revision") > -1 ||                                                  // NOI18N
              (msg.indexOf("unable to find repository location for") > -1 && msg.indexOf("in revision") > -1);  // NOI18N
    }
    
    public static boolean isPathNotFound(String msg) {
        msg = msg.toLowerCase();
        return msg.indexOf("path not found") > -1;  // NOI18N
    }
        
    private static boolean isAlreadyAWorkingCopy(String msg) {   
        msg = msg.toLowerCase();       
        return msg.indexOf("is already a working copy for a different url") > -1;           // NOI18N
    }

    private static boolean isClosedConnection(String msg) {
        msg = msg.toLowerCase();       
        return msg.indexOf("could not read status line: an existing connection was forcibly closed by the remote host.") > -1; // NOI18N
    }

    private static boolean isCommitFailed(String msg) {
        msg = msg.toLowerCase();       
        return msg.indexOf("commit failed (details follow)") > -1;                          // NOI18N
    }

    public static boolean isFileAlreadyExists(String msg) {
        msg = msg.toLowerCase();        
        return msg.indexOf("file already exists") > -1 ||                                   // NOI18N
               (msg.indexOf("mkcol") > -1 && isHTTP405(msg));                               // NOI18N
    }
    
    private static boolean isOutOfDate(String msg) {
        msg = msg.toLowerCase();       
        return msg.indexOf("out of date") > -1 || msg.indexOf("out-of-date") > -1;                                             // NOI18N
    }
    
    public static boolean isNoCliSvnClient(String msg) {
        msg = msg.toLowerCase();
        return (msg.indexOf("command line client adapter is not available") > -1) || 
               (msg.indexOf(CommandlineClient.ERR_CLI_NOT_AVALABLE) > -1);
    }

    public static boolean isUnsupportedJavaHl(String msg) {
        msg = msg.toLowerCase();
        return msg.indexOf(CommandlineClient.ERR_JAVAHL_NOT_SUPPORTED) > -1;
    }

    public static boolean isMissingOrLocked(String msg) {
        msg = msg.toLowerCase();
        int idx = msg.indexOf("svn: working copy");                                         // NOI18N
        if(idx > -1) {
            return msg.indexOf("is missing or not locked", idx + 17) > -1;                  // NOI18N
        }
        idx = msg.indexOf("svn: directory");                                                // NOI18N
        if(idx > -1) {
            return msg.indexOf("is missing", idx + 13) > -1;                                // NOI18N
        }
        return false;
    }

    /**
     * Determines if the message is a result of an on-direcotory-called command.
     * @param msg error message
     * @return true if msg is a message returned from a command called on a directory, false otherwise.
     */
    public static boolean isTargetDirectory(String msg) {
        msg = msg.toLowerCase();
        return (msg.indexOf("refers to a directory") > -1);                                         // NOI18N
    }

    /**
     * Is relocating to a wrong repository URL?
     * @param msg
     * @return
     */
    public static boolean isWrongUUID(String msg) {
        msg = msg.toLowerCase();
        return (msg.contains("has uuid") && msg.contains("but the wc has")); //NOI18N
    }
    
    public static boolean isNotUnderVersionControl (String message) {
        message = message.toLowerCase();
        return message.contains("is not under version control"); //NOI18N
    }

    public static boolean isNodeUnderVersionControl (String message) {
        message = message.toLowerCase();
        return message.contains("is not under version control"); //NOI18N
    }
    
    public static boolean isNodeNotFound (String message) {
        message = message.toLowerCase();
        return (message.contains(": the node") && message.contains("not found")); //NOI18N
    }
    
    public static boolean isPartOfNewerWC (String message) {
        message = message.toLowerCase();
        return message.contains("this client is too old to work with the working copy"); //NOI18N
    }

    public static boolean isTooOldWorkingCopy (String message) {
        message = message.toLowerCase();
        return message.contains("working copy") //NOI18N
                && message.contains("is too old") // NOI18N
                || message.contains("working copy needs to be upgraded"); //NOI18N
    }

    public static void notifyException(Exception ex, boolean annotate, boolean isUI) {
        String message = ex.getMessage();
        if (isUI && isPartOfNewerWC(message)) {
            if (switchToCommandlineClient(message)) {
                return;
            }
        }
        if (isUI && isTooOldWorkingCopy(message)) {
            if (upgrade(message)) {
                return;
            }
        }
        if(isNoCliSvnClient(message)) {
            if(isUI) {
                notifyNoClient();
            }
            return;
        }
        if(isCancelledAction(message)) {
            Subversion.LOG.log(Level.FINE, message, ex);
            return;
        }                   
        Subversion.LOG.log(Level.INFO, message, ex);
        if( annotate ) {
            String msg = getCustomizedMessage(ex);  
            if(msg == null) {
                if(ex instanceof SVNClientException) {
                    msg = parseExceptionMessage((SVNClientException) ex);
                } else {
                    msg = message;                        
                }                
            }        
            annotate(msg);
        }         
    }       

    public static boolean handleLogException(SVNUrl url, SVNRevision revision, SVNClientException e) {
        String protocol = url.getProtocol();
        if(  ( protocol.startsWith("https") && SvnClientExceptionHandler.isSecureConnTruncated(e.getMessage()) ) ||                    
             ( protocol.startsWith("http") && SvnClientExceptionHandler.isReportOf200(e.getMessage())              ) ||
             ( ( protocol.startsWith("file") || protocol.startsWith("svn+") ) && SvnClientExceptionHandler.isFileNotFoundInRevision(e.getMessage()) ) ) 
        {            
            Subversion.LOG.log(Level.INFO, e.getMessage(), e);    // keep track
            annotate(NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_ErrorFileNotFoundInRevision", new String[] {revision.toString()} )); // NOI18N                      
            return true;
        } 
        return false;
    }     
    
    private static void notifyNoClient() {
        MissingClient msc = new MissingClient();
        msc.show();
    }
    
    @NbBundle.Messages({
        "MSG_Error_TooOldWC=Working copy is too old for the currently used Subversion client.\n"
            + "You'll need to manually upgrade the working copy."
    })
    private static String getCustomizedMessage(Exception exception) {
        String exMsg = exception.getMessage();
        String msg = exMsg.toLowerCase();
        if (isHTTP405(msg)) {
            msg = exMsg + "\n\n" + NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error405");                                // NOI18N
        } else if(isOutOfDate(msg) || isMissingOrLocked(msg)) {
            msg = exMsg + "\n\n" + org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_OutOfDate") + "\n"; // NOI18N
        } else if(isWrongUUID(msg)) {
            msg = exMsg + "\n\n" + org.openide.util.NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_RelocateWrongUUID") + "\n"; // NOI18N
        } else if (isTooOldWorkingCopy(msg) && (msg.contains("svn upgrade")
                || msg.contains("working copy format of ") && msg.contains("is too old") //NOI18N
                || msg.contains("needs to be upgraded"))) { //NOI18N
            msg = Bundle.MSG_Error_TooOldWC() + "\n\n" + exMsg + "\n"; //NOI18N
        } else {
            msg = null;
        }
        return msg;
    }

    public static String parseExceptionMessage(SVNClientException ex) {
        String msg = ex.getMessage();
        msg = msg.replace("svn: warning: ", "");
        msg = msg.replace("svn: ", "");
        if (isTooOldClientForWC(msg)) {
            // add an additional message for old clients
            msg += NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_Error_OldClient");    // NOI18N
        }
        return msg;
    }

    public static void annotate(String msg) {        
        CommandReport report = new CommandReport(NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_SubversionCommandError"), msg);
        JButton ok = new JButton(NbBundle.getMessage(SvnClientExceptionHandler.class, "CTL_CommandReport_OK"));
        NotifyDescriptor descriptor = new NotifyDescriptor(
                report, 
                NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_CommandFailed_Title"), 
                NotifyDescriptor.DEFAULT_OPTION,
                NotifyDescriptor.ERROR_MESSAGE,
                new Object [] { ok },
                ok);
        DialogDisplayer.getDefault().notify(descriptor);        
    }

    static void handleInvalidKeyException(InvalidKeyException ike) {
        Subversion.LOG.log(Level.INFO, ike.getMessage(), ike);
        String msg = NbBundle.getMessage(SvnClientExceptionHandler.class, "MSG_InvalidKeyException"); // NOI18N
        annotate(msg);
    }

    private boolean isBadRecordMac (String message) {
        message = message.toLowerCase();
        return message.contains("received fatal alert")                 //NOI18N
                && message.contains("bad_record_mac");                  //NOI18N
    }

    private static boolean upgrade (final String exMessage) {
        boolean retval = false;
        final File wc = findPathInTooOldWCMessage(exMessage);
        if (wc != null) {
            retval = Mutex.EVENT.readAccess(new Mutex.Action() {
                @Override
                public Boolean run () {
                    if (WorkingCopyAttributesCache.getInstance().logAskedToUpgrade(wc)) {
                        // displayed for the first time
                        SystemAction.get(UpgradeAction.class).upgrade(wc);
                        return true;
                    } else {
                        // do not bother user any more
                        return false;
                    }
                }
            });
        }
        return retval;
    }
    
    private static File findPathInTooOldWCMessage (String message) {
        for (String s : new String[] { ".*Working copy \'([^\']+)\' is too old.*" }) { //NOI18N
            Pattern p = Pattern.compile(s, Pattern.DOTALL);
            Matcher m = p.matcher(message);
            if (m.matches()) {
                return new File(m.group(1));
            }
        }
        return null;
    }
    
    private static boolean WARNING_WC_TOO_NEW_DISPLAYED;
    @NbBundle.Messages({
        "CTL_WC18SwitchToCmd=&OK",
        "LBL_Error_WCUnsupportedFormat=Subversion Working Copy Format"
    })
    private static boolean switchToCommandlineClient (final String exMessage) {
        boolean retval = false;
        if (!SvnClientFactory.isCLI()) {
            retval = Mutex.EVENT.readAccess(new Mutex.Action() {
                @Override
                public Boolean run () {
                    if (WARNING_WC_TOO_NEW_DISPLAYED) {
                        return false;
                    }                    
                    WARNING_WC_TOO_NEW_DISPLAYED = true;
                    JButton okButton = new JButton();
                    Mnemonics.setLocalizedText(okButton, Bundle.CTL_WC18SwitchToCmd());
                    NotifyHtmlPanel p = new NotifyHtmlPanel();
                    p.setText(format18WCMessage(exMessage));
                    NotifyDescriptor descriptor = new NotifyDescriptor(
                            p, 
                            Bundle.LBL_Error_WCUnsupportedFormat(),
                            NotifyDescriptor.OK_CANCEL_OPTION,
                            NotifyDescriptor.QUESTION_MESSAGE,
                            new Object [] { okButton, NotifyDescriptor.CANCEL_OPTION },
                            okButton);
                    if (okButton == DialogDisplayer.getDefault().notify(descriptor)) {
                        SvnClientFactory.switchToCLI();
                        return true;
                    } else {
                        return false;
                    }
                }
            });
        }
        return retval;
    }

    @NbBundle.Messages({
        "# {0} - client type",
        "MSG_Error_WC1.8Format=

A Subversion working copy is version 1.8 format (or later). " + "The IDE needs to set the CLI client as the default instead of the {0} client to work with " + "Subversion 1.8 repositories. When the new {0} client is available you can download the update " + "from the Update Center and restore the {0} client as the default.

To set the CLI Subversion " + "client as the default for all Subversion repositories, click OK.

" + "

See Subversion " + "1.8 FAQ for more information.

", "# {0} - client type", "# {1} - working copy path", "MSG_Error_WC1.8Format.path=

The Subversion working copy at \"{1}\" is version " + "1.8 format (or later). The IDE needs to set the CLI client as the default instead of the {0} client " + "to work with Subversion 1.8 repositories. When the new {0} client is available you can download " + "the update from the Update Center and restore the {0} client as the default.

" + "

To set the CLI Subversion client as the default for all Subversion repositories, click OK.

" + "

See Subversion " + "1.8 FAQ for more information.

", "MSG_Client_Type.svnkit=SVNKit", "MSG_Client_Type.javahl=JavaHL" }) private static String format18WCMessage (String msg) { String location = null; //NOI18N msg = msg.toLowerCase().replace("\r\n", "\n").replace("\r", "\n").replace("\n", " "); //NOI18N for (String s : new String[] { ".*working copy at \'([^\']+)\'.*" }) { //NOI18N Pattern p = Pattern.compile(s, Pattern.DOTALL); Matcher m = p.matcher(msg); if (m.matches()) { location = m.group(1); break; } } String formatted; if (location == null) { formatted = Bundle.MSG_Error_WC1_8Format(SvnClientFactory.isJavaHl() ? Bundle.MSG_Client_Type_javahl() : Bundle.MSG_Client_Type_svnkit()); } else { formatted = Bundle.MSG_Error_WC1_8Format_path(SvnClientFactory.isJavaHl() ? Bundle.MSG_Client_Type_javahl() : Bundle.MSG_Client_Type_svnkit(), location); } return formatted; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy