/*
* ====================================================================
* Copyright (c) 2004-2009 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.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.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;
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 = -1;
}
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));
length -= read;
if (read > 0) {
getOutputStream().write(buffer, 0, read);
} else {
break;
}
}
getOutputStream().flush();
} finally {
myRepository.getDebugLog().flushStream(getOutputStream());
}
}
public SVNAuthentication getLastValidCredentials() {
return myLastValidAuth;
}
public void clearAuthenticationCache() {
myLastValidAuth = null;
myTrustManager = null;
myKeyManager = 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 {
if ("".equals(path) || path == null) {
path = "/";
}
// 1. prompt for ssl client cert if needed, if cancelled - throw cancellation exception.
HTTPSSLKeyManager keyManager = myKeyManager == null && myRepository.getAuthenticationManager() != null ? createKeyManager() : myKeyManager;
TrustManager trustManager = myTrustManager == null && myRepository.getAuthenticationManager() != null ? myRepository.getAuthenticationManager().getTrustManager(myRepository.getLocation()) : myTrustManager;
String sslRealm = "<" + myHost.getProtocol() + "://" + myHost.getHost() + ":" + myHost.getPort() + ">";
SVNAuthentication httpAuth = myLastValidAuth;
boolean isAuthForced = myRepository.getAuthenticationManager() != null ? myRepository.getAuthenticationManager().isAuthenticationForced() : false;
if (httpAuth == null && isAuthForced) {
httpAuth = myRepository.getAuthenticationManager().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();
}
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);
}
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--;
}
}
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);
try {
myProxyAuthentication = HTTPAuthentication.parseAuthParameters(proxyAuthHeaders, myProxyAuthentication, myCharset);
} 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();
ISVNAuthenticationManager authManager = myRepository.getAuthenticationManager();
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 = myRepository.getAuthenticationManager() != null ? myRepository.getAuthenticationManager().isAuthenticationForced() : false;
if (isAuthForced) {
if (httpAuth != null && myChallengeCredentials != null && !HTTPAuthentication.isSchemeSupportedByServer(myChallengeCredentials.getAuthenticationScheme(), authHeaderValues)) {
skip = true;
}
}
try {
myChallengeCredentials = HTTPAuthentication.parseAuthParameters(authHeaderValues, myChallengeCredentials, myCharset);
} catch (SVNException svne) {
err = svne.getErrorMessage();
break;
}
myChallengeCredentials.setChallengeParameter("methodname", 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;
}
ISVNAuthenticationManager authManager = myRepository.getAuthenticationManager();
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) {
close();
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();
} 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();
ISVNAuthenticationManager authManager = myRepository.getAuthenticationManager();
ISVNProxyManager proxyManager = authManager != null ? authManager.getProxyManager(location) : null;
if (proxyManager != null) {
proxyManager.acknowledgeProxyContext(true, null);
}
}
if (httpAuth != null && realm != null && myRepository.getAuthenticationManager() != null) {
myRepository.getAuthenticationManager().acknowledgeAuthentication(true, ISVNAuthenticationManager.PASSWORD, realm, null, httpAuth);
}
if (trustManager != null && myRepository.getAuthenticationManager() != null) {
myRepository.getAuthenticationManager().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().info(err.getMessage());
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 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) {
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) {
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 (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 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 = SAXParserFactory.newInstance();
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 = SAXParserFactory.newInstance();
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 void setSpoolResponse(boolean spoolResponse) {
myIsSpoolResponse = spoolResponse;
}
}