/*
* ====================================================================
* 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;
}
}