/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. 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. * ==================================================================== */ 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.CookieHandler; import java.net.HttpURLConnection; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.text.ParseException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; 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.SSLSession; import javax.net.ssl.SSLSocket; 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.SVNCancelException; 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.BasicAuthenticationManager; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManagerExt; import org.tmatesoft.svn.core.auth.ISVNProxyManager; import org.tmatesoft.svn.core.auth.ISVNProxyManagerEx; 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.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.ISVNDebugLog; 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. */ public 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 final static Map<String,List<String>> emptyHeader = Collections.unmodifiableMap(Collections.<String, List<String>>emptyMap()); 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 boolean myLogSSLParams; 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 int myRequestCount; private HTTPStatus myLastStatus; 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 HTTPStatus getLastStatus() { return myLastStatus; } public SVNURL getHost() { return myHost; } private void connect(HTTPSSLKeyManager keyManager, TrustManager trustManager, ISVNProxyManager proxyManager) 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(); 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 (proxyManager != null && proxyManager.getProxyHost() != null) { final ISVNDebugLog debugLog = myRepository.getDebugLog(); debugLog.logFine(SVNLogType.NETWORK, "Using proxy " + proxyManager.getProxyHost() + " (secured=" + myIsSecured + ")"); mySocket = SVNSocketFactory.createPlainSocket(proxyManager.getProxyHost(), proxyManager.getProxyPort(), connectTimeout, readTimeout, myRepository.getCanceller()); myIsProxied = true; if (myIsSecured) { int authAttempts = 0; boolean credentialsUsed = false; while(true) { if (mySocket == null) { mySocket = SVNSocketFactory.createPlainSocket(proxyManager.getProxyHost(), proxyManager.getProxyPort(), connectTimeout, readTimeout, myRepository.getCanceller()); debugLog.logFine(SVNLogType.NETWORK, "proxy connection reopened"); } HTTPRequest connectRequest = new HTTPRequest(myCharset); connectRequest.setConnection(this); if (myProxyAuthentication != null) { final String authToken = myProxyAuthentication.authenticate(); connectRequest.setProxyAuthentication(authToken); debugLog.logFine(SVNLogType.NETWORK, "auth token set: " + authToken); } 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; myProxyAuthentication = null; mySocket = SVNSocketFactory.createSSLSocket(keyManager != null ? new KeyManager[] { keyManager } : new KeyManager[0], trustManager, host, port, mySocket, readTimeout); proxyManager.acknowledgeProxyContext(true, null); return; } else if (status.getCode() == HttpURLConnection.HTTP_PROXY_AUTH) { if (hasToCloseConnection(connectRequest.getResponseHeader())) { close(); debugLog.logFine(SVNLogType.NETWORK, "Connection closed as requested by the response header"); } authAttempts++; debugLog.logFine(SVNLogType.NETWORK, "authentication attempt #" + authAttempts); Collection<String> proxyAuthHeaders = connectRequest.getResponseHeader().getHeaderValues(HTTPHeader.PROXY_AUTHENTICATE_HEADER); Collection<String> authTypes = Arrays.asList("Basic", "Digest", "Negotiate", "NTLM"); debugLog.logFine(SVNLogType.NETWORK, "authentication methods supported: " + authTypes); try { myProxyAuthentication = HTTPAuthentication.parseAuthParameters(proxyAuthHeaders, myProxyAuthentication, myCharset, authTypes, null, myRequestCount); } catch (SVNException svne) { myRepository.getDebugLog().logFine(SVNLogType.NETWORK, svne); close(); throw svne; } debugLog.logFine(SVNLogType.NETWORK, "authentication type chosen: " + myProxyAuthentication.getClass().getSimpleName()); connectRequest.initCredentials(myProxyAuthentication, "CONNECT", host + ":" + port); HTTPNTLMAuthentication ntlmProxyAuth = null; HTTPNegotiateAuthentication negotiateProxyAuth = null; if (myProxyAuthentication instanceof HTTPNTLMAuthentication) { ntlmProxyAuth = (HTTPNTLMAuthentication) myProxyAuthentication; if (ntlmProxyAuth.isInType3State()) { debugLog.logFine(SVNLogType.NETWORK, "continuation of NTLM authentication"); continue; } } else if (myProxyAuthentication instanceof HTTPNegotiateAuthentication) { negotiateProxyAuth = (HTTPNegotiateAuthentication) myProxyAuthentication; if (negotiateProxyAuth.isStarted()) { debugLog.logFine(SVNLogType.NETWORK, "continuation of Negotiate authentication"); continue; } } if (ntlmProxyAuth != null && authAttempts == 1) { if (!ntlmProxyAuth.allowPropmtForCredentials()) { continue; } } if (negotiateProxyAuth != null && !negotiateProxyAuth.needsLogin()) { debugLog.logFine(SVNLogType.NETWORK, "Negotiate will use existing credentials"); continue; } if (!credentialsUsed) { myProxyAuthentication.setCredentials(SVNPasswordAuthentication.newInstance(proxyManager.getProxyUserName(), getProxyPasswordValue(proxyManager), false, myRepository.getLocation(), false)); debugLog.logFine(SVNLogType.NETWORK, "explicit credentials set"); credentialsUsed = true; } else { debugLog.logFine(SVNLogType.NETWORK, "no more credentials to try"); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "HTTP proxy authorization failed"); if (proxyManager != null) { proxyManager.acknowledgeProxyContext(false, err); } SVNErrorManager.error(err, SVNLogType.NETWORK); } } else { SVNURL proxyURL = SVNURL.parseURIEncoded("http://" + proxyManager.getProxyHost() + ":" + proxyManager.getProxyPort()); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "{0} request failed on ''{1}''", new Object[] {"CONNECT", proxyURL}); proxyManager.acknowledgeProxyContext(false, err); SVNErrorManager.error(err, connectRequest.getErrorMessage(), SVNLogType.NETWORK); } } } else if (proxyManager.getProxyUserName() != null && getProxyPasswordValue(proxyManager) != null ){ myProxyAuthentication = new HTTPBasicAuthentication("UTF-8"); myProxyAuthentication.setCredentials(SVNPasswordAuthentication.newInstance(proxyManager.getProxyUserName(), getProxyPasswordValue(proxyManager), false, myRepository.getLocation(), false)); debugLog.logFine(SVNLogType.NETWORK, "explicit credentials set"); } } 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()); myLogSSLParams = true; } } } private char[] getProxyPasswordValue(ISVNProxyManager proxyManager) { if (proxyManager == null) { return null; } else if (proxyManager instanceof ISVNProxyManagerEx) { return ((ISVNProxyManagerEx) proxyManager).getProxyPasswordValue(); } else { final String password = proxyManager.getProxyPassword(); if (password == null) { return null; } return password.toCharArray(); } } 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() { clearLastValidAuth(); myTrustManager = null; myKeyManager = null; myChallengeCredentials = null; myProxyAuthentication = null; myRequestCount = 0; } private void clearLastValidAuth() { if (myLastValidAuth != null) { myLastValidAuth.dismissSensitiveData(); } myLastValidAuth = null; } 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 { myLastStatus = null; 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; ISVNProxyManager proxyManager = authManager != null ? authManager.getProxyManager(myRepository.getLocation()) : null; String sslRealm = composeRealm(""); 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) { 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; URI actualURI = null; try { actualURI = new URI(myRepository.getLocation().toString()); } catch (URISyntaxException e) { myRepository.getDebugLog().logError(SVNLogType.NETWORK, e); } while(retryCount >= 0) { connect(keyManager, trustManager, proxyManager); 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); } final CookieHandler cookieHandler = CookieHandler.getDefault(); if (cookieHandler != null && actualURI != null) { final Map<String, List<String>> cookieHeader = cookieHandler.get(actualURI, emptyHeader); if (cookieHeader != null) { request.setCookies(cookieHeader); } } if (mySocket instanceof SSLSocket && myLogSSLParams) { final SSLSession session = ((SSLSocket)mySocket).getSession(); if (session != null) { myRepository.getDebugLog().logFine(SVNLogType.NETWORK, "Connected to " + myRepository.getLocation() + " using " + session.getProtocol()); myLogSSLParams = false; } } try { request.dispatch(method, path, header, ok1, ok2, context); break; } catch (EOFException pe) { myRepository.getDebugLog().logFine(SVNLogType.NETWORK, pe); // retry, EOF always means closed connection. if (retryCount > 0) { close(); continue; } throw (IOException) new IOException(pe.getMessage()).initCause(pe); } finally { retryCount--; } } final CookieHandler cookieHandler = CookieHandler.getDefault(); if(cookieHandler != null && actualURI != null){ cookieHandler.put(actualURI, request.getResponseHeader().getRawHeaders()); } myNextRequestTimeout = request.getNextRequestTimeout(); myLastStatus = request.getStatus(); } catch (SSLHandshakeException ssl) { myRepository.getDebugLog().logFine(SVNLogType.NETWORK, ssl); close(); if (ssl.getCause() instanceof SVNSSLUtil.CertificateNotTrustedException || ssl.getCause() instanceof SVNSSLUtil.CertificateDoesNotConformConstraints) { 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.isInitialized()) { keyManager.acknowledgeAndClearAuthentication(sslErr); } else { sslErr = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "SSL handshake failed: ''{0}''", new Object[] { ssl.getMessage() }, SVNErrorMessage.TYPE_ERROR, ssl); SVNErrorManager.error(sslErr, SVNLogType.NETWORK); } 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); } else { err = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, e); } } 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) { if (proxyManager != null) { proxyManager.acknowledgeProxyContext(false, err); } close(); break; } if (proxyManager != null) { proxyManager.acknowledgeProxyContext(true, err); } if (keyManager != null) { myKeyManager = keyManager; myTrustManager = trustManager; keyManager.acknowledgeAndClearAuthentication(null); } if (myLastStatus.getCode() == HttpURLConnection.HTTP_FORBIDDEN) { if (httpAuth != null && authManager != null) { BasicAuthenticationManager.acknowledgeAuthentication(false, ISVNAuthenticationManager.PASSWORD, realm, request.getErrorMessage(), httpAuth, myRepository.getLocation(), authManager); } clearLastValidAuth(); close(); err = request.getErrorMessage(); } else if (myIsProxied && myLastStatus.getCode() == HttpURLConnection.HTTP_PROXY_AUTH) { Collection<String> proxyAuthHeaders = request.getResponseHeader().getHeaderValues(HTTPHeader.PROXY_AUTHENTICATE_HEADER); Collection<String> 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"); if (proxyManager != null) { proxyManager.acknowledgeProxyContext(false, err); } close(); break; } else if (myLastStatus.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { authAttempts++;//how many times did we try? Collection<String> authHeaderValues = request.getResponseHeader().getHeaderValues(HTTPHeader.AUTHENTICATE_HEADER); if (authHeaderValues == null || authHeaderValues.size() == 0) { err = request.getErrorMessage(); myLastStatus.setError(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, err.getMessageTemplate(), err.getRelatedObjects())); if ("LOCK".equalsIgnoreCase(method)) { myLastStatus.getError().setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Probably you are trying to lock file in repository that only allows anonymous access")); } SVNErrorManager.error(myLastStatus.getError(), SVNLogType.NETWORK); return myLastStatus; } //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<String> 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) { clearLastValidAuth(); continue; } } else if (myChallengeCredentials instanceof HTTPNegotiateAuthentication) { negoAuthIsRequired = true; negoAuth = (HTTPNegotiateAuthentication)myChallengeCredentials; if (negoAuth.isStarted()) { continue; } } clearLastValidAuth(); if (ntlmAuth != null && 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. */ if (!ntlmAuth.allowPropmtForCredentials()) { continue; } } if (negoAuth != null && !negoAuth.needsLogin()) { continue; } if (authManager == null) { err = request.getErrorMessage(); break; } realm = myChallengeCredentials.getChallengeParameter("realm"); realm = realm == null ? "" : " " + realm; realm = composeRealm(realm); if (httpAuth == null) { httpAuth = authManager.getFirstAuthentication(ISVNAuthenticationManager.PASSWORD, realm, myRepository.getLocation()); } else if (authAttempts >= requestAttempts) { BasicAuthenticationManager.acknowledgeAuthentication(false, ISVNAuthenticationManager.PASSWORD, realm, request.getErrorMessage(), httpAuth, myRepository.getLocation(), authManager); httpAuth = authManager.getNextAuthentication(ISVNAuthenticationManager.PASSWORD, realm, myRepository.getLocation()); } if (httpAuth == null) { err = SVNErrorMessage.create(SVNErrorCode.CANCELLED, new SVNCancelException(SVNErrorMessage.create(SVNErrorCode.CANCELLED, "ISVNAuthentication provider did not provide credentials; HTTP authorization cancelled."))); break; } if (httpAuth != null) { myChallengeCredentials.setCredentials((SVNPasswordAuthentication) httpAuth); } continue; } else if (myLastStatus.getCode() == HttpURLConnection.HTTP_MOVED_PERM || myLastStatus.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) { if (proxyManager != null) { proxyManager.acknowledgeProxyContext(true, null); } } if (httpAuth != null && realm != null && authManager != null) { BasicAuthenticationManager.acknowledgeAuthentication(true, ISVNAuthenticationManager.PASSWORD, realm, null, httpAuth, myRepository.getLocation(), authManager); } if (trustManager != null && authManager != null) { authManager.acknowledgeTrustManager(trustManager); } if (httpAuth != null) { myLastValidAuth = httpAuth.copy(); } if (myLastStatus.getCodeClass() == 2 && authManager instanceof ISVNAuthenticationManagerExt) { ((ISVNAuthenticationManagerExt) authManager).acknowledgeConnectionSuccessful(myRepository.getLocation(), method); } myLastStatus.setHeader(request.getResponseHeader()); return myLastStatus; } // 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 String composeRealm(String realm) { final StringBuffer buffer = new StringBuffer(); buffer.append("<"); buffer.append(myHost.getProtocol()); buffer.append("://"); if (myHost.getUserInfo() != null && !"".equals(myHost.getUserInfo().trim())) { buffer.append(myHost.getUserInfo()); buffer.append("@"); } buffer.append(myHost.getHost()); buffer.append(":"); buffer.append(myHost.getPort()); buffer.append(">"); if (realm != null) { buffer.append(realm); } return buffer.toString(); } private boolean isClearCredentialsOnClose(HTTPAuthentication auth) { return !(auth instanceof HTTPBasicAuthentication || auth instanceof HTTPDigestAuthentication || auth instanceof HTTPNegotiateAuthentication); } 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(); } else { clearLastValidAuth(); } if (mySocket != null) { if (myInputStream != null) { try { myInputStream.close(); } catch (IOException e) {} } 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 CancellableSocketInputStream(new BufferedInputStream(mySocket.getInputStream(), 2048), myRepository.getCanceller()); myInputStream = new BufferedInputStream(mySocket.getInputStream(), 2048); } return myInputStream; } private OutputStream getOutputStream() throws IOException { SVNDebugLog.getDefaultLog().logFine(SVNLogType.DEFAULT, "socket output stream requested..."); 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<String, Object> supportedFeatures = new HashMap<String, Object>(); 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<String> names = supportedFeatures.keySet().iterator(); names.hasNext();) { String name = 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; } public void setSpoolAll(boolean spoolAll) { myIsSpoolAll = spoolAll; } public void setSpoolDirectory(File spoolDirectory) { mySpoolDirectory = spoolDirectory; } }