Revision: 2578
http://sourceforge.net/p/gatewiki/code/2578
Author: ian_roberts
Date: 2022-03-28 10:35:26 +0000 (Mon, 28 Mar 2022)
Log Message:
-----------
Added patched version of one internal svnkit class that was causing failures when talking to https repositories using the BouncyCastle TLS implementation.
Added Paths:
-----------
trunk/cow/src/java/org/
trunk/cow/src/java/org/tmatesoft/
trunk/cow/src/java/org/tmatesoft/svn/
trunk/cow/src/java/org/tmatesoft/svn/core/
trunk/cow/src/java/org/tmatesoft/svn/core/internal/
trunk/cow/src/java/org/tmatesoft/svn/core/internal/io/
trunk/cow/src/java/org/tmatesoft/svn/core/internal/io/dav/
trunk/cow/src/java/org/tmatesoft/svn/core/internal/io/dav/http/
trunk/cow/src/java/org/tmatesoft/svn/core/internal/io/dav/http/HTTPConnection.java
Added: trunk/cow/src/java/org/tmatesoft/svn/core/internal/io/dav/http/HTTPConnection.java
===================================================================
--- trunk/cow/src/java/org/tmatesoft/svn/core/internal/io/dav/http/HTTPConnection.java (rev 0)
+++ trunk/cow/src/java/org/tmatesoft/svn/core/internal/io/dav/http/HTTPConnection.java 2022-03-28 10:35:26 UTC (rev 2578)
@@ -0,0 +1,1071 @@
+/*
+ * This is a slightly modified version of the stock class from svnkit
+ * 1.3.6, to remove the explicit call to flush() in the close() method.
+ * This call is un-necessary anyway as it is immediately followed by
+ * closing the stream in question, and its inclusion causes issues
+ * when using the BouncyCastle TLS implementation, which throws an
+ * IllegalStateException rather than an IOException if the stream is
+ * flushed too early.
+ *
+ * Modifications are (c) Ian Roberts for the GATE team, the original svnkit
+ * copyright notice for the rest of the file follows..
+ */
+
+/*
+ * ====================================================================
+ * Copyright (c) 2004-2011 TMate Software Ltd. All rights reserved.
+ *
+ * This software is licensed as described [below]. The terms
+ * are also available at http://svnkit.com/license.html
+ * If newer versions of this license are posted there, you may use a
+ * newer version instead, at your option.
+ * ====================================================================
+ */
+
+/*
+ * The TMate License
+ *
+ * This license applies to all portions of TMate SVNKit library, which
+ * are not externally-maintained libraries (e.g. Trilead SSH library).
+ *
+ * All the source code and compiled classes in package org.tigris.subversion.javahl
+ * except SvnClient class are covered by the license in JAVAHL-LICENSE file
+ *
+ * Copyright (c) 2004-2011 TMate Software. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Redistributions in any form must be accompanied by information on how to
+ * obtain complete source code for the software that uses SVNKit and any
+ * accompanying software that uses the software that uses SVNKit. The source
+ * code must either be included in the distribution or be available for no
+ * more than the cost of distribution plus a nominal fee, and must be freely
+ * redistributable under reasonable conditions. For an executable file, complete
+ * source code means the source code for all modules it contains. It does not
+ * include source code for modules or files that typically accompany the major
+ * components of the operating system on which the executable file runs.
+ *
+ * * Redistribution in any form without redistributing source code for software
+ * that uses SVNKit is possible only when such redistribution is explictly permitted
+ * by TMate Software. Please, contact TMate Software at su...@sv... to
+ * get such permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY TMATE SOFTWARE ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE
+ * DISCLAIMED.
+ *
+ * IN NO EVENT SHALL TMATE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.tmatesoft.svn.core.internal.io.dav.http;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManager;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNErrorMessage;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
+import org.tmatesoft.svn.core.auth.ISVNProxyManager;
+import org.tmatesoft.svn.core.auth.SVNAuthentication;
+import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication;
+import org.tmatesoft.svn.core.internal.io.dav.handlers.DAVErrorHandler;
+import org.tmatesoft.svn.core.internal.util.ChunkedInputStream;
+import org.tmatesoft.svn.core.internal.util.FixedSizeInputStream;
+import org.tmatesoft.svn.core.internal.util.SVNHashMap;
+import org.tmatesoft.svn.core.internal.util.SVNSSLUtil;
+import org.tmatesoft.svn.core.internal.util.SVNSocketFactory;
+import org.tmatesoft.svn.core.internal.wc.DefaultSVNAuthenticationManager;
+import org.tmatesoft.svn.core.internal.wc.IOExceptionWrapper;
+import org.tmatesoft.svn.core.internal.wc.SVNCancellableOutputStream;
+import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
+import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
+import org.tmatesoft.svn.core.io.SVNRepository;
+import org.tmatesoft.svn.util.SVNDebugLog;
+import org.tmatesoft.svn.util.SVNLogType;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * @version 1.3
+ * @author TMate Software Ltd.
+ */
+class HTTPConnection implements IHTTPConnection {
+
+ private static final DefaultHandler DEFAULT_SAX_HANDLER = new DefaultHandler();
+ private static EntityResolver NO_ENTITY_RESOLVER = new EntityResolver() {
+ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+ return new InputSource(new ByteArrayInputStream(new byte[0]));
+ }
+ };
+
+
+ private static final int requestAttempts;
+ private static final int DEFAULT_HTTP_TIMEOUT = 3600*1000;
+
+ static {
+ String attemptsString = System.getProperty("svnkit.http.requestAttempts", "1" );
+ int attempts = 1;
+ try {
+ attempts = Integer.parseInt(attemptsString);
+ } catch (NumberFormatException nfe) {
+ attempts = 1;
+ }
+ if (attempts <= 0) {
+ attempts = 1;
+ }
+ requestAttempts = attempts;
+ }
+
+ private static SAXParserFactory ourSAXParserFactory;
+ private byte[] myBuffer;
+ private SAXParser mySAXParser;
+ private SVNURL myHost;
+ private OutputStream myOutputStream;
+ private InputStream myInputStream;
+ private Socket mySocket;
+ private SVNRepository myRepository;
+ private boolean myIsSecured;
+ private boolean myIsProxied;
+ private SVNAuthentication myLastValidAuth;
+ private HTTPAuthentication myChallengeCredentials;
+ private HTTPAuthentication myProxyAuthentication;
+ private boolean myIsSpoolResponse;
+ private TrustManager myTrustManager;
+ private HTTPSSLKeyManager myKeyManager;
+ private String myCharset;
+ private boolean myIsSpoolAll;
+ private File mySpoolDirectory;
+ private long myNextRequestTimeout;
+ private Collection myCookies;
+ private int myRequestCount;
+
+ public HTTPConnection(SVNRepository repository, String charset, File spoolDirectory, boolean spoolAll) throws SVNException {
+ myRepository = repository;
+ myCharset = charset;
+ myHost = repository.getLocation().setPath("", false);
+ myIsSecured = "https".equalsIgnoreCase(myHost.getProtocol());
+ myIsSpoolAll = spoolAll;
+ mySpoolDirectory = spoolDirectory;
+ myNextRequestTimeout = Long.MAX_VALUE;
+ }
+
+ public SVNURL getHost() {
+ return myHost;
+ }
+
+ private void connect(HTTPSSLKeyManager keyManager, TrustManager trustManager) throws IOException, SVNException {
+ SVNURL location = myRepository.getLocation();
+
+ if (mySocket == null || SVNSocketFactory.isSocketStale(mySocket)) {
+ close();
+ String host = location.getHost();
+ int port = location.getPort();
+
+ ISVNAuthenticationManager authManager = myRepository.getAuthenticationManager();
+ ISVNProxyManager proxyAuth = authManager != null ? authManager.getProxyManager(location) : null;
+ int connectTimeout = authManager != null ? authManager.getConnectTimeout(myRepository) : 0;
+ int readTimeout = authManager != null ? authManager.getReadTimeout(myRepository) : DEFAULT_HTTP_TIMEOUT;
+ if (readTimeout < 0) {
+ readTimeout = DEFAULT_HTTP_TIMEOUT;
+ }
+ if (proxyAuth != null && proxyAuth.getProxyHost() != null) {
+ myRepository.getDebugLog().logFine(SVNLogType.NETWORK, "Using proxy " + proxyAuth.getProxyHost() + " (secured=" + myIsSecured + ")");
+ mySocket = SVNSocketFactory.createPlainSocket(proxyAuth.getProxyHost(), proxyAuth.getProxyPort(), connectTimeout, readTimeout, myRepository.getCanceller());
+ if (myProxyAuthentication == null) {
+ myProxyAuthentication = new HTTPBasicAuthentication(proxyAuth.getProxyUserName(), proxyAuth.getProxyPassword(), myCharset);
+ }
+ myIsProxied = true;
+ if (myIsSecured) {
+ HTTPRequest connectRequest = new HTTPRequest(myCharset);
+ connectRequest.setConnection(this);
+ connectRequest.initCredentials(myProxyAuthentication, "CONNECT", host + ":" + port);
+ connectRequest.setProxyAuthentication(myProxyAuthentication.authenticate());
+ connectRequest.setForceProxyAuth(true);
+ connectRequest.dispatch("CONNECT", host + ":" + port, null, 0, 0, null);
+ HTTPStatus status = connectRequest.getStatus();
+ if (status.getCode() == HttpURLConnection.HTTP_OK) {
+ myInputStream = null;
+ myOutputStream = null;
+ mySocket = SVNSocketFactory.createSSLSocket(keyManager != null ? new KeyManager[] { keyManager } : new KeyManager[0], trustManager, host, port, mySocket, readTimeout);
+ proxyAuth.acknowledgeProxyContext(true, null);
+ return;
+ }
+ SVNURL proxyURL = SVNURL.parseURIEncoded("http://" + proxyAuth.getProxyHost() + ":" + proxyAuth.getProxyPort());
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "{0} request failed on ''{1}''", new Object[] {"CONNECT", proxyURL});
+ proxyAuth.acknowledgeProxyContext(false, err);
+ SVNErrorManager.error(err, connectRequest.getErrorMessage(), SVNLogType.NETWORK);
+ }
+ } else {
+ myIsProxied = false;
+ myProxyAuthentication = null;
+ mySocket = myIsSecured ?
+ SVNSocketFactory.createSSLSocket(keyManager != null ? new KeyManager[] { keyManager } : new KeyManager[0], trustManager, host, port, connectTimeout, readTimeout, myRepository.getCanceller()) :
+ SVNSocketFactory.createPlainSocket(host, port, connectTimeout, readTimeout, myRepository.getCanceller());
+ }
+ }
+ }
+
+ public void readHeader(HTTPRequest request) throws IOException {
+ InputStream is = myRepository.getDebugLog().createLogStream(SVNLogType.NETWORK, getInputStream());
+
+ try {
+ // may throw EOF exception.
+ HTTPStatus status = HTTPParser.parseStatus(is, myCharset);
+ HTTPHeader header = HTTPHeader.parseHeader(is, myCharset);
+ request.setStatus(status);
+ request.setResponseHeader(header);
+ } catch (ParseException e) {
+ // in case of parse exception:
+ // try to read remaining and log it.
+ String line = HTTPParser.readLine(is, myCharset);
+ while(line != null && line.length() > 0) {
+ line = HTTPParser.readLine(is, myCharset);
+ }
+
+ throw new IOException(e.getMessage());
+ } finally {
+ myRepository.getDebugLog().flushStream(is);
+ }
+ }
+
+ public SVNErrorMessage readError(HTTPRequest request, String method, String path) {
+ DAVErrorHandler errorHandler = new DAVErrorHandler();
+ try {
+ readData(request, method, path, errorHandler);
+ } catch (IOException e) {
+ return null;
+ }
+ return errorHandler.getErrorMessage();
+ }
+
+ public void sendData(byte[] body) throws IOException {
+ try {
+ getOutputStream().write(body, 0, body.length);
+ getOutputStream().flush();
+ } finally {
+ myRepository.getDebugLog().flushStream(getOutputStream());
+ }
+ }
+
+ public void sendData(InputStream source, long length) throws IOException {
+ try {
+ byte[] buffer = getBuffer();
+ while(length > 0) {
+ int read = source.read(buffer, 0, (int) Math.min(buffer.length, length));
+ if (read > 0) {
+ length -= read;
+ getOutputStream().write(buffer, 0, read);
+ } else if (read < 0) {
+ break;
+ }
+ }
+ getOutputStream().flush();
+ } finally {
+ myRepository.getDebugLog().flushStream(getOutputStream());
+ }
+ }
+
+ public SVNAuthentication getLastValidCredentials() {
+ return myLastValidAuth;
+ }
+
+ public void clearAuthenticationCache() {
+ myCookies = null;
+ myLastValidAuth = null;
+ myTrustManager = null;
+ myKeyManager = null;
+ myChallengeCredentials = null;
+ myProxyAuthentication = null;
+ myRequestCount = 0;
+ }
+
+ public HTTPStatus request(String method, String path, HTTPHeader header, StringBuffer body, int ok1, int ok2, OutputStream dst, DefaultHandler handler) throws SVNException {
+ return request(method, path, header, body, ok1, ok2, dst, handler, null);
+ }
+
+ public HTTPStatus request(String method, String path, HTTPHeader header, StringBuffer body, int ok1, int ok2, OutputStream dst, DefaultHandler handler, SVNErrorMessage context) throws SVNException {
+ byte[] buffer = null;
+ if (body != null) {
+ try {
+ buffer = body.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ buffer = body.toString().getBytes();
+ }
+ }
+ return request(method, path, header, buffer != null ? new ByteArrayInputStream(buffer) : null, ok1, ok2, dst, handler, context);
+ }
+
+ public HTTPStatus request(String method, String path, HTTPHeader header, InputStream body, int ok1, int ok2, OutputStream dst, DefaultHandler handler) throws SVNException {
+ return request(method, path, header, body, ok1, ok2, dst, handler, null);
+ }
+
+ public HTTPStatus request(String method, String path, HTTPHeader header, InputStream body, int ok1, int ok2, OutputStream dst, DefaultHandler handler, SVNErrorMessage context) throws SVNException {
+ myRequestCount++;
+
+ if ("".equals(path) || path == null) {
+ path = "/";
+ }
+
+ ISVNAuthenticationManager authManager = myRepository.getAuthenticationManager();
+
+ // 1. prompt for ssl client cert if needed, if cancelled - throw cancellation exception.
+ HTTPSSLKeyManager keyManager = myKeyManager == null && authManager != null ? createKeyManager() : myKeyManager;
+ TrustManager trustManager = myTrustManager == null && authManager != null ? authManager.getTrustManager(myRepository.getLocation()) : myTrustManager;
+
+ String sslRealm = "<" + myHost.getProtocol() + "://" + myHost.getHost() + ":" + myHost.getPort() + ">";
+ SVNAuthentication httpAuth = myLastValidAuth;
+ boolean isAuthForced = authManager != null ? authManager.isAuthenticationForced() : false;
+ if (httpAuth == null && isAuthForced) {
+ httpAuth = authManager.getFirstAuthentication(ISVNAuthenticationManager.PASSWORD, sslRealm, null);
+ myChallengeCredentials = new HTTPBasicAuthentication((SVNPasswordAuthentication)httpAuth, myCharset);
+ }
+ String realm = null;
+
+ // 2. create request instance.
+ HTTPRequest request = new HTTPRequest(myCharset);
+ request.setConnection(this);
+ request.setKeepAlive(true);
+ request.setRequestBody(body);
+ request.setResponseHandler(handler);
+ request.setResponseStream(dst);
+
+ SVNErrorMessage err = null;
+ boolean ntlmAuthIsRequired = false;
+ boolean ntlmProxyAuthIsRequired = false;
+ boolean negoAuthIsRequired = false;
+ int authAttempts = 0;
+ while (true) {
+ HTTPStatus status = null;
+ if (myNextRequestTimeout < 0 || System.currentTimeMillis() >= myNextRequestTimeout) {
+ SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "Keep-Alive timeout detected");
+ close();
+ if (isClearCredentialsOnClose(myChallengeCredentials)) {
+ httpAuth = null;
+ }
+ }
+ int retryCount = 1;
+ try {
+ err = null;
+ String httpAuthResponse = null;
+ String proxyAuthResponse = null;
+ while(retryCount >= 0) {
+ connect(keyManager, trustManager);
+ request.reset();
+ request.setProxied(myIsProxied);
+ request.setSecured(myIsSecured);
+ if (myProxyAuthentication != null && (ntlmProxyAuthIsRequired || !"NTLM".equals(myProxyAuthentication.getAuthenticationScheme()))) {
+ if (proxyAuthResponse == null) {
+ request.initCredentials(myProxyAuthentication, method, path);
+ proxyAuthResponse = myProxyAuthentication.authenticate();
+ }
+ request.setProxyAuthentication(proxyAuthResponse);
+ }
+
+ if (myChallengeCredentials != null && (ntlmAuthIsRequired || negoAuthIsRequired || ((!"NTLM".equals(myChallengeCredentials.getAuthenticationScheme())) && !"Negotiate".equals(myChallengeCredentials.getAuthenticationScheme())) &&
+ httpAuth != null)) {
+ if (httpAuthResponse == null) {
+ request.initCredentials(myChallengeCredentials, method, path);
+ httpAuthResponse = myChallengeCredentials.authenticate();
+ }
+ request.setAuthentication(httpAuthResponse);
+ }
+
+ if (myCookies != null && !myCookies.isEmpty()) {
+ request.setCookies(myCookies);
+ }
+ try {
+ request.dispatch(method, path, header, ok1, ok2, context);
+ break;
+ } catch (EOFException pe) {
+ // retry, EOF always means closed connection.
+ if (retryCount > 0) {
+ close();
+ continue;
+ }
+ throw (IOException) new IOException(pe.getMessage()).initCause(pe);
+ } finally {
+ retryCount--;
+ }
+ }
+ if (request.getResponseHeader().hasHeader(HTTPHeader.SET_COOKIE)) {
+ myCookies = request.getResponseHeader().getHeaderValues(HTTPHeader.COOKIE);
+ }
+ myNextRequestTimeout = request.getNextRequestTimeout();
+ status = request.getStatus();
+ } catch (SSLHandshakeException ssl) {
+ myRepository.getDebugLog().logFine(SVNLogType.NETWORK, ssl);
+ close();
+ if (ssl.getCause() instanceof SVNSSLUtil.CertificateNotTrustedException) {
+ SVNErrorManager.cancel(ssl.getCause().getMessage(), SVNLogType.NETWORK);
+ }
+ SVNErrorMessage sslErr = SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "SSL handshake failed: ''{0}''", new Object[] { ssl.getMessage() }, SVNErrorMessage.TYPE_ERROR, ssl);
+ if (keyManager != null) {
+ keyManager.acknowledgeAndClearAuthentication(sslErr);
+ }
+ err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, ssl);
+ continue;
+ } catch (IOException e) {
+ myRepository.getDebugLog().logFine(SVNLogType.NETWORK, e);
+ if (e instanceof SocketTimeoutException) {
+ err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED,
+ "timed out waiting for server", null, SVNErrorMessage.TYPE_ERROR, e);
+ } else if (e instanceof UnknownHostException) {
+ err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED,
+ "unknown host", null, SVNErrorMessage.TYPE_ERROR, e);
+ } else if (e instanceof ConnectException) {
+ err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED,
+ "connection refused by the server", null,
+ SVNErrorMessage.TYPE_ERROR, e);
+ } else if (e instanceof SVNCancellableOutputStream.IOCancelException) {
+ SVNErrorManager.cancel(e.getMessage(), SVNLogType.NETWORK);
+ } else if (e instanceof SSLException) {
+ err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED,
+ e.getMessage());
+ } else {
+ err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED,
+ e.getMessage());
+ }
+ } catch (SVNException e) {
+ myRepository.getDebugLog().logFine(SVNLogType.NETWORK, e);
+ // force connection close on SVNException
+ // (could be thrown by user's auth manager methods).
+ close();
+ throw e;
+ } finally {
+ finishResponse(request);
+ }
+
+ if (err != null) {
+ close();
+ break;
+ }
+
+ if (keyManager != null) {
+ myKeyManager = keyManager;
+ myTrustManager = trustManager;
+ keyManager.acknowledgeAndClearAuthentication(null);
+ }
+
+ if (status.getCode() == HttpURLConnection.HTTP_FORBIDDEN) {
+ myLastValidAuth = null;
+ close();
+ err = request.getErrorMessage();
+ } else if (myIsProxied && status.getCode() == HttpURLConnection.HTTP_PROXY_AUTH) {
+ Collection proxyAuthHeaders = request.getResponseHeader().getHeaderValues(HTTPHeader.PROXY_AUTHENTICATE_HEADER);
+ Collection authTypes = null;
+ if (authManager != null && authManager instanceof DefaultSVNAuthenticationManager) {
+ DefaultSVNAuthenticationManager defaultAuthManager = (DefaultSVNAuthenticationManager) authManager;
+ authTypes = defaultAuthManager.getAuthTypes(myRepository.getLocation());
+ }
+ try {
+ myProxyAuthentication = HTTPAuthentication.parseAuthParameters(proxyAuthHeaders, myProxyAuthentication, myCharset, authTypes, null, myRequestCount);
+ } catch (SVNException svne) {
+ myRepository.getDebugLog().logFine(SVNLogType.NETWORK, svne);
+ err = svne.getErrorMessage();
+ break;
+ }
+
+ if (myProxyAuthentication instanceof HTTPNTLMAuthentication) {
+ ntlmProxyAuthIsRequired = true;
+ HTTPNTLMAuthentication ntlmProxyAuth = (HTTPNTLMAuthentication)myProxyAuthentication;
+ if (ntlmProxyAuth.isInType3State()) {
+ continue;
+ }
+ }
+
+ err = SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "HTTP proxy authorization failed");
+ SVNURL location = myRepository.getLocation();
+ ISVNProxyManager proxyManager = authManager != null ? authManager.getProxyManager(location) : null;
+ if (proxyManager != null) {
+ proxyManager.acknowledgeProxyContext(false, err);
+ }
+ close();
+
+ break;
+ } else if (status.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ authAttempts++;//how many times did we try?
+
+ Collection authHeaderValues = request.getResponseHeader().getHeaderValues(HTTPHeader.AUTHENTICATE_HEADER);
+ if (authHeaderValues == null || authHeaderValues.size() == 0) {
+ err = request.getErrorMessage();
+ status.setError(SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, err.getMessageTemplate(), err.getRelatedObjects()));
+ if ("LOCK".equalsIgnoreCase(method)) {
+ status.getError().setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE,
+ "Probably you are trying to lock file in repository that only allows anonymous access"));
+ }
+ SVNErrorManager.error(status.getError(), SVNLogType.NETWORK);
+ return status;
+ }
+
+ //we should work around a situation when a server
+ //does not support Basic authentication while we're
+ //forcing it, credentials should not be immediately
+ //thrown away
+ boolean skip = false;
+ isAuthForced = authManager != null ? authManager.isAuthenticationForced() : false;
+ if (isAuthForced) {
+ if (httpAuth != null && myChallengeCredentials != null && !HTTPAuthentication.isSchemeSupportedByServer(myChallengeCredentials.getAuthenticationScheme(), authHeaderValues)) {
+ skip = true;
+ }
+ }
+
+ Collection authTypes = null;
+ if (authManager != null && authManager instanceof DefaultSVNAuthenticationManager) {
+ DefaultSVNAuthenticationManager defaultAuthManager = (DefaultSVNAuthenticationManager) authManager;
+ authTypes = defaultAuthManager.getAuthTypes(myRepository.getLocation());
+ }
+
+ try {
+ myChallengeCredentials = HTTPAuthentication.parseAuthParameters(authHeaderValues, myChallengeCredentials, myCharset, authTypes, authManager, myRequestCount);
+ } catch (SVNException svne) {
+ err = svne.getErrorMessage();
+ break;
+ }
+
+ myChallengeCredentials.setChallengeParameter("method", method);
+ myChallengeCredentials.setChallengeParameter("uri", HTTPParser.getCanonicalPath(path, null).toString());
+
+ if (skip) {
+ close();
+ continue;
+ }
+
+ HTTPNTLMAuthentication ntlmAuth = null;
+ HTTPNegotiateAuthentication negoAuth = null;
+ if (myChallengeCredentials instanceof HTTPNTLMAuthentication) {
+ ntlmAuthIsRequired = true;
+ ntlmAuth = (HTTPNTLMAuthentication)myChallengeCredentials;
+ if (ntlmAuth.isInType3State()) {
+ continue;
+ }
+ } else if (myChallengeCredentials instanceof HTTPDigestAuthentication) {
+ // continue (retry once) if previous request was acceppted?
+ if (myLastValidAuth != null) {
+ myLastValidAuth = null;
+ continue;
+ }
+ } else if (myChallengeCredentials instanceof HTTPNegotiateAuthentication) {
+ negoAuthIsRequired = true;
+ negoAuth = (HTTPNegotiateAuthentication)myChallengeCredentials;
+ if (negoAuth.isStarted()) {
+ continue;
+ }
+ }
+
+ myLastValidAuth = null;
+
+ if (ntlmAuth != null && ntlmAuth.isNative() && authAttempts == 1) {
+ /*
+ * if this is the first time we get HTTP_UNAUTHORIZED, NTLM is the target auth scheme
+ * and JNA is available, we should try a native auth mechanism first without calling
+ * auth providers.
+ */
+ continue;
+ }
+
+ if (negoAuth != null && !negoAuth.needsLogin()) {
+ continue;
+ }
+
+ if (authManager == null) {
+ err = request.getErrorMessage();
+ break;
+ }
+
+ realm = myChallengeCredentials.getChallengeParameter("realm");
+ realm = realm == null ? "" : " " + realm;
+ realm = "<" + myHost.getProtocol() + "://" + myHost.getHost() + ":" + myHost.getPort() + ">" + realm;
+
+ if (httpAuth == null) {
+ httpAuth = authManager.getFirstAuthentication(ISVNAuthenticationManager.PASSWORD, realm, myRepository.getLocation());
+ } else if (authAttempts >= requestAttempts) {
+ authManager.acknowledgeAuthentication(false, ISVNAuthenticationManager.PASSWORD, realm, request.getErrorMessage(), httpAuth);
+ httpAuth = authManager.getNextAuthentication(ISVNAuthenticationManager.PASSWORD, realm, myRepository.getLocation());
+ }
+
+ if (httpAuth == null) {
+ err = SVNErrorMessage.create(SVNErrorCode.CANCELLED, "HTTP authorization cancelled");
+ break;
+ }
+ if (httpAuth != null) {
+ myChallengeCredentials.setCredentials((SVNPasswordAuthentication) httpAuth);
+ }
+ continue;
+ } else if (status.getCode() == HttpURLConnection.HTTP_MOVED_PERM || status.getCode() == HttpURLConnection.HTTP_MOVED_TEMP) {
+ String newLocation = request.getResponseHeader().getFirstHeaderValue(HTTPHeader.LOCATION_HEADER);
+ if (newLocation == null) {
+ err = request.getErrorMessage();
+ break;
+ }
+ int hostIndex = newLocation.indexOf("://");
+ if (hostIndex > 0) {
+ hostIndex += 3;
+ hostIndex = newLocation.indexOf("/", hostIndex);
+ }
+ if (hostIndex > 0 && hostIndex < newLocation.length()) {
+ String newPath = newLocation.substring(hostIndex);
+ if (newPath.endsWith("/") &&
+ !newPath.endsWith("//") && !path.endsWith("/") &&
+ newPath.substring(0, newPath.length() - 1).equals(path)) {
+ path += "//";
+ continue;
+ }
+ }
+ err = request.getErrorMessage();
+ close();
+ } else if (request.getErrorMessage() != null) {
+ err = request.getErrorMessage();
+ } else {
+ ntlmProxyAuthIsRequired = false;
+ ntlmAuthIsRequired = false;
+ negoAuthIsRequired = false;
+ }
+
+ if (err != null) {
+ break;
+ }
+
+ if (myIsProxied) {
+ SVNURL location = myRepository.getLocation();
+ ISVNProxyManager proxyManager = authManager != null ? authManager.getProxyManager(location) : null;
+ if (proxyManager != null) {
+ proxyManager.acknowledgeProxyContext(true, null);
+ }
+ }
+
+ if (httpAuth != null && realm != null && authManager != null) {
+ authManager.acknowledgeAuthentication(true, ISVNAuthenticationManager.PASSWORD, realm, null, httpAuth);
+ }
+ if (trustManager != null && authManager != null) {
+ authManager.acknowledgeTrustManager(trustManager);
+ }
+
+ if (httpAuth != null) {
+ myLastValidAuth = httpAuth;
+ }
+
+ status.setHeader(request.getResponseHeader());
+ return status;
+ }
+ // force close on error that was not processed before.
+ // these are errors that has no relation to http status (processing error or cancellation).
+ close();
+ if (err != null && err.getErrorCode().getCategory() != SVNErrorCode.RA_DAV_CATEGORY &&
+ err.getErrorCode() != SVNErrorCode.UNSUPPORTED_FEATURE) {
+ SVNErrorManager.error(err, SVNLogType.NETWORK);
+ }
+ // err2 is another default context...
+ myRepository.getDebugLog().logFine(SVNLogType.NETWORK, new Exception(err.getMessage()));
+ SVNErrorMessage err2 = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "{0} request failed on ''{1}''", new Object[] {method, path}, err.getType(), err.getCause());
+ SVNErrorManager.error(err, err2, SVNLogType.NETWORK);
+ return null;
+ }
+
+ private boolean isClearCredentialsOnClose(HTTPAuthentication auth) {
+ return !(auth instanceof HTTPBasicAuthentication || auth instanceof HTTPDigestAuthentication);
+ }
+
+ private HTTPSSLKeyManager createKeyManager() {
+ if (!myIsSecured) {
+ return null;
+ }
+
+ SVNURL location = myRepository.getLocation();
+ ISVNAuthenticationManager authManager = myRepository.getAuthenticationManager();
+ String sslRealm = "<" + location.getProtocol() + "://" + location.getHost() + ":" + location.getPort() + ">";
+ return new HTTPSSLKeyManager(authManager, sslRealm, location);
+ }
+
+ public SVNErrorMessage readData(HTTPRequest request, OutputStream dst) throws IOException {
+ InputStream stream = createInputStream(request.getResponseHeader(), getInputStream());
+ byte[] buffer = getBuffer();
+ boolean willCloseConnection = false;
+ try {
+ while (true) {
+ int count = stream.read(buffer);
+ if (count < 0) {
+ break;
+ }
+ if (dst != null) {
+ dst.write(buffer, 0, count);
+ }
+ }
+ } catch (IOException e) {
+ willCloseConnection = true;
+ if (e instanceof IOExceptionWrapper) {
+ IOExceptionWrapper wrappedException = (IOExceptionWrapper) e;
+ return wrappedException.getOriginalException().getErrorMessage();
+ }
+ if (e.getCause() instanceof SVNException) {
+ return ((SVNException) e.getCause()).getErrorMessage();
+ }
+ throw e;
+ } finally {
+ if (!willCloseConnection) {
+ SVNFileUtil.closeFile(stream);
+ }
+ myRepository.getDebugLog().flushStream(stream);
+ }
+ return null;
+ }
+
+ public SVNErrorMessage readData(HTTPRequest request, String method, String path, DefaultHandler handler) throws IOException {
+ InputStream is = null;
+ SpoolFile tmpFile = null;
+ SVNErrorMessage err = null;
+ try {
+ if (myIsSpoolResponse || myIsSpoolAll) {
+ OutputStream dst = null;
+ try {
+ tmpFile = new SpoolFile(mySpoolDirectory);
+ dst = tmpFile.openForWriting();
+ dst = new SVNCancellableOutputStream(dst, myRepository.getCanceller());
+ // this will exhaust http stream anyway.
+ err = readData(request, dst);
+ SVNFileUtil.closeFile(dst);
+ dst = null;
+ if (err != null) {
+ return err;
+ }
+ // this stream always have to be closed.
+ is = tmpFile.openForReading();
+ } finally {
+ SVNFileUtil.closeFile(dst);
+ }
+ } else {
+ is = createInputStream(request.getResponseHeader(), getInputStream());
+ }
+ // this will not close is stream.
+ err = readData(is, method, path, handler);
+ } catch (IOException e) {
+ throw e;
+ } finally {
+ if (myIsSpoolResponse || myIsSpoolAll) {
+ // always close spooled stream.
+ SVNFileUtil.closeFile(is);
+ } else if (err == null && !hasToCloseConnection(request.getResponseHeader())) {
+ // exhaust stream if connection is not about to be closed.
+ SVNFileUtil.closeFile(is);
+ }
+ if (tmpFile != null) {
+ try {
+ tmpFile.delete();
+ } catch (SVNException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+ myIsSpoolResponse = false;
+ }
+ return err;
+ }
+
+ private SVNErrorMessage readData(InputStream is, String method, String path, DefaultHandler handler) throws FactoryConfigurationError, UnsupportedEncodingException, IOException {
+ try {
+ if (mySAXParser == null) {
+ mySAXParser = getSAXParserFactory().newSAXParser();
+ }
+ XMLReader reader = new XMLReader(is);
+ while (!reader.isClosed()) {
+ org.xml.sax.XMLReader xmlReader = mySAXParser.getXMLReader();
+ xmlReader.setContentHandler(handler);
+ xmlReader.setDTDHandler(handler);
+ xmlReader.setErrorHandler(handler);
+ xmlReader.setEntityResolver(NO_ENTITY_RESOLVER);
+ xmlReader.parse(new InputSource(reader));
+ }
+ } catch (SAXException e) {
+ mySAXParser = null;
+ if (e instanceof SAXParseException) {
+ if (handler instanceof DAVErrorHandler) {
+ // failed to read svn-specific error, return null.
+ return null;
+ }
+ } else if (e.getException() instanceof SVNException) {
+ return ((SVNException) e.getException()).getErrorMessage();
+ } else if (e.getCause() instanceof SVNException) {
+ return ((SVNException) e.getCause()).getErrorMessage();
+ }
+ return SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "Processing {0} request response failed: {1} ({2}) ", new Object[] {method, e.getMessage(), path});
+ } catch (ParserConfigurationException e) {
+ mySAXParser = null;
+ return SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "XML parser configuration error while processing {0} request response: {1} ({2}) ", new Object[] {method, e.getMessage(), path});
+ } catch (EOFException e) {
+ // skip it.
+ } finally {
+ if (mySAXParser != null) {
+ // to avoid memory leaks when connection is cached.
+ org.xml.sax.XMLReader xmlReader = null;
+ try {
+ xmlReader = mySAXParser.getXMLReader();
+ } catch (SAXException e) {
+ }
+ if (xmlReader != null) {
+ xmlReader.setContentHandler(DEFAULT_SAX_HANDLER);
+ xmlReader.setDTDHandler(DEFAULT_SAX_HANDLER);
+ xmlReader.setErrorHandler(DEFAULT_SAX_HANDLER);
+ xmlReader.setEntityResolver(NO_ENTITY_RESOLVER);
+ }
+ }
+ myRepository.getDebugLog().flushStream(is);
+ }
+ return null;
+ }
+
+ public void skipData(HTTPRequest request) throws IOException {
+ if (hasToCloseConnection(request.getResponseHeader())) {
+ return;
+ }
+ InputStream is = createInputStream(request.getResponseHeader(), getInputStream());
+ while(is.skip(2048) > 0);
+ }
+
+ public void close() {
+ if (isClearCredentialsOnClose(myChallengeCredentials)) {
+ clearAuthenticationCache();
+ }
+ if (mySocket != null) {
+ if (myInputStream != null) {
+ try {
+ myInputStream.close();
+ } catch (IOException e) {}
+ }
+/* IR commented this out as it isn't needed and it breaks BouncyCastle
+ if (myOutputStream != null) {
+ try {
+ myOutputStream.flush();
+ } catch (IOException e) {}
+ }
+*/
+ if (myOutputStream != null) {
+ try {
+ myOutputStream.close();
+ } catch (IOException e) {}
+ }
+ try {
+ mySocket.close();
+ } catch (IOException e) {}
+ mySocket = null;
+ myOutputStream = null;
+ myInputStream = null;
+ }
+ }
+
+ private byte[] getBuffer() {
+ if (myBuffer == null) {
+ myBuffer = new byte[32*1024];
+ }
+ return myBuffer;
+ }
+
+ private InputStream getInputStream() throws IOException {
+ if (myInputStream == null) {
+ if (mySocket == null) {
+ return null;
+ }
+ myInputStream = new BufferedInputStream(mySocket.getInputStream(), 2048);
+ }
+ return myInputStream;
+ }
+
+ private OutputStream getOutputStream() throws IOException {
+ if (myOutputStream == null) {
+ if (mySocket == null) {
+ return null;
+ }
+ myOutputStream = new BufferedOutputStream(mySocket.getOutputStream(), 2048);
+ myOutputStream = myRepository.getDebugLog().createLogStream(SVNLogType.NETWORK, myOutputStream);
+ }
+ return myOutputStream;
+ }
+
+ private void finishResponse(HTTPRequest request) {
+ if (myOutputStream != null) {
+ try {
+ myOutputStream.flush();
+ } catch (IOException ex) {
+ }
+ }
+ HTTPHeader header = request != null ? request.getResponseHeader() : null;
+ if (hasToCloseConnection(header)) {
+ close();
+ }
+ }
+
+ private static boolean hasToCloseConnection(HTTPHeader header) {
+ if (header == null) {
+ return true;
+ }
+
+ String connectionHeader = header.getFirstHeaderValue(HTTPHeader.CONNECTION_HEADER);
+ String proxyHeader = header.getFirstHeaderValue(HTTPHeader.PROXY_CONNECTION_HEADER);
+
+ if (connectionHeader != null && connectionHeader.toLowerCase().indexOf("close") >= 0) {
+ return true;
+ } else if (proxyHeader != null && proxyHeader.toLowerCase().indexOf("close") >= 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private InputStream createInputStream(HTTPHeader readHeader, InputStream is) throws IOException {
+ if ("chunked".equalsIgnoreCase(readHeader.getFirstHeaderValue(HTTPHeader.TRANSFER_ENCODING_HEADER))) {
+ is = new ChunkedInputStream(is, myCharset);
+ } else if (readHeader.getFirstHeaderValue(HTTPHeader.CONTENT_LENGTH_HEADER) != null) {
+ String lengthStr = readHeader.getFirstHeaderValue(HTTPHeader.CONTENT_LENGTH_HEADER);
+ long length = 0;
+ try {
+ length = Long.parseLong(lengthStr);
+ } catch (NumberFormatException nfe) {
+ length = 0;
+ }
+ is = new FixedSizeInputStream(is, length);
+ } else if (!hasToCloseConnection(readHeader)) {
+ // no content length and no valid transfer-encoding!
+ // consider as empty response.
+
+ // but only when there is no "Connection: close" or "Proxy-Connection: close" header,
+ // in that case just return "is".
+ // skipData will not read that as it should also analyze "close" instruction.
+
+ // return empty stream.
+ // and force connection close? (not to read garbage on the next request).
+ is = new FixedSizeInputStream(is, 0);
+ // this will force connection to close.
+ readHeader.setHeaderValue(HTTPHeader.CONNECTION_HEADER, "close");
+ }
+
+ if ("gzip".equals(readHeader.getFirstHeaderValue(HTTPHeader.CONTENT_ENCODING_HEADER))) {
+ is = new GZIPInputStream(is);
+ }
+ return myRepository.getDebugLog().createLogStream(SVNLogType.NETWORK, is);
+ }
+
+ private static synchronized SAXParserFactory getSAXParserFactory() throws FactoryConfigurationError {
+ if (ourSAXParserFactory == null) {
+ ourSAXParserFactory = createSAXParserFactory();
+ Map supportedFeatures = new SVNHashMap();
+ try {
+ ourSAXParserFactory.setFeature("http://xml.org/sax/features/namespaces", true);
+ supportedFeatures.put("http://xml.org/sax/features/namespaces", Boolean.TRUE);
+ } catch (SAXNotRecognizedException e) {
+ } catch (SAXNotSupportedException e) {
+ } catch (ParserConfigurationException e) {
+ }
+ try {
+ ourSAXParserFactory.setFeature("http://xml.org/sax/features/validation", false);
+ supportedFeatures.put("http://xml.org/sax/features/validation", Boolean.FALSE);
+ } catch (SAXNotRecognizedException e) {
+ } catch (SAXNotSupportedException e) {
+ } catch (ParserConfigurationException e) {
+ }
+ try {
+ ourSAXParserFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ supportedFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", Boolean.FALSE);
+ } catch (SAXNotRecognizedException e) {
+ } catch (SAXNotSupportedException e) {
+ } catch (ParserConfigurationException e) {
+ }
+ if (supportedFeatures.size() < 3) {
+ ourSAXParserFactory = createSAXParserFactory();
+ for (Iterator names = supportedFeatures.keySet().iterator(); names.hasNext();) {
+ String name = (String) names.next();
+ try {
+ ourSAXParserFactory.setFeature(name, supportedFeatures.get(name) == Boolean.TRUE);
+ } catch (SAXNotRecognizedException e) {
+ } catch (SAXNotSupportedException e) {
+ } catch (ParserConfigurationException e) {
+ }
+ }
+ }
+ ourSAXParserFactory.setNamespaceAware(true);
+ ourSAXParserFactory.setValidating(false);
+ }
+ return ourSAXParserFactory;
+ }
+
+ public static SAXParserFactory createSAXParserFactory() {
+ String legacy = System.getProperty("svnkit.sax.useDefault");
+ if (legacy == null || !Boolean.valueOf(legacy).booleanValue()) {
+ return SAXParserFactory.newInstance();
+ }
+ // instantiate JVM parser.
+ String[] parsers = {"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", // 1.5, 1.6
+ "org.apache.crimson.jaxp.SAXParserFactoryImpl", // 1.4
+ };
+ for (int i = 0; i < parsers.length; i++) {
+ String className = parsers[i];
+ ClassLoader loader = HTTPConnection.class.getClassLoader();
+ try {
+ Class clazz = null;
+ if (loader != null) {
+ clazz = loader.loadClass(className);
+ } else {
+ clazz = Class.forName(className);
+ }
+ if (clazz != null) {
+ Object factory = clazz.newInstance();
+ if (factory instanceof SAXParserFactory) {
+ return (SAXParserFactory) factory;
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ } catch (InstantiationException e) {
+ } catch (IllegalAccessException e) {
+ }
+ }
+ return SAXParserFactory.newInstance();
+ }
+
+ public void setSpoolResponse(boolean spoolResponse) {
+ myIsSpoolResponse = spoolResponse;
+ }
+
+}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|