? org/apache/commons/httpclient/ConnectMethod.java Index: org/apache/commons/httpclient/Authenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v retrieving revision 1.17 diff -u -w -r1.17 Authenticator.java Index: org/apache/commons/httpclient/HttpClient.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v retrieving revision 1.48 diff -u -w -r1.48 HttpClient.java --- org/apache/commons/httpclient/HttpClient.java 14 Jul 2002 04:40:00 -0000 1.48 +++ org/apache/commons/httpclient/HttpClient.java 17 Jul 2002 15:09:13 -0000 @@ -64,9 +64,8 @@ import java.io.IOException; import java.net.URL; - -import org.apache.commons.httpclient.log.Log; -import org.apache.commons.httpclient.log.LogSource; +import javax.net.ssl.SSLSocketFactory; +import org.apache.commons.httpclient.log.*; /** *
@@ -80,6 +79,7 @@
* @author dIon Gillard
* @version $Revision: 1.48 $ $Date: 2002/07/14 04:40:00 $
*/
+
public class HttpClient {
@@ -108,6 +108,10 @@
*/
private HttpState state;
+ private SSLSocketFactory sslSocketFactory = null;
+
+ private int timeout = 0;
+
// ------------------------------------------------------------- Properties
/**
@@ -133,6 +137,23 @@
this.state = state;
}
+ /**
+ * Specifies an alternative factory for SSL sockets.
+ * @see HttpConnection#setSSLSocketFactory(SSLSocketFactory) HttpConnection.setSSLSocketFactory
+ * @param sslSocketFactory a living instance of the alternative SSLSocketFactory
+ */
+ public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
+ this.sslSocketFactory = sslSocketFactory;
+ }
+
+ /**
+ * Sets the SO_TIMEOUT which is the timeout for waiting for data.
+ * @param timeout Timeout in milliseconds
+ */
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
// --------------------------------------------------------- Public Methods
/**
@@ -311,7 +332,12 @@
}
if (!connection.isOpen()) {
+ connection.setSSLSocketFactory(sslSocketFactory);
+ connection.setSoTimeout(timeout);
connection.open();
+ if (connection.isProxied() && connection.isSecure()) {
+ method = new ConnectMethod(method);
+ }
}
return method.execute(getState(),connection);
}
Index: org/apache/commons/httpclient/HttpConnection.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v
retrieving revision 1.10
diff -u -w -r1.10 HttpConnection.java
--- org/apache/commons/httpclient/HttpConnection.java 14 Jul 2002 05:16:06 -0000 1.10
+++ org/apache/commons/httpclient/HttpConnection.java 17 Jul 2002 15:09:14 -0000
@@ -69,9 +69,10 @@
import java.net.Socket;
import java.net.SocketException;
import javax.net.ssl.SSLSocketFactory;
+import javax.net.SocketFactory;
-import org.apache.commons.httpclient.log.Log;
-import org.apache.commons.httpclient.log.LogSource;
+import org.apache.commons.httpclient.log.*;
+import java.lang.reflect.Method;
/**
@@ -144,6 +145,18 @@
// ------------------------------------------ Attribute Setters and Getters
/**
+ * Specifies an alternative factory for SSL sockets. If factory
+ * is null the default implementation is used.
+ *
+ * @param factory An instance of a SSLSocketFactory or null.
+ * @throws IllegalStateException If called after the connection was opened
+ */
+ public void setSSLSocketFactory(SSLSocketFactory factory) {
+ assertNotOpen();
+ sslSocketFactory = factory;
+ }
+
+ /**
* Return my host.
*
* @return my host.
@@ -283,7 +296,7 @@
*/
public void setSoTimeout(int timeout) throws SocketException,
IllegalStateException {
- log.debug("HttpConnection.setSoTimeout()");
+ log.debug("HttpConnection.setSoTimeout("+ timeout +")");
_so_timeout = timeout;
if(_socket != null){
_socket.setSoTimeout(timeout);
@@ -303,10 +316,15 @@
if (null == _socket) {
String host = (null == _proxyHost) ? _host : _proxyHost;
int port = (null == _proxyHost) ? _port : _proxyPort;
- if (_ssl) {
- _socket = SSLSocketFactory.getDefault().createSocket(host,port);
+ if (isSecure() && !isProxied()) {
+ if (sslSocketFactory == null) {
+ sslSocketFactory = SSLSocketFactory.getDefault();
+ }
+ _socket = sslSocketFactory.createSocket(host,port);
+ _usingSecureSocket = true;
} else {
_socket = new Socket(host,port);
+ _usingSecureSocket = false;
}
}
_socket.setSoTimeout(_so_timeout);
@@ -322,6 +340,42 @@
}
/**
+ * Calling this method indicates that the proxy has successfully created
+ * the tunnel to the host. The socket will be switched to the secure socket.
+ * Subsequent communication is done via the secure socket. The method can only
+ * be called once on a proxied secure connection.
+ *
+ * @throws IllegalStateException if connection is not secure and proxied or
+ * if the socket is already secure.
+ * @throws IOException if an error occured creating the secure socket
+ */
+ public void tunnelCreated() throws IllegalStateException, IOException {
+ if (!isSecure() || !isProxied()) throw new IllegalStateException("Connection must be secure and proxied to use this feature");
+ if (_usingSecureSocket) throw new IllegalStateException("Already using a secure socket");
+
+ if (sslSocketFactory == null) {
+ sslSocketFactory = SSLSocketFactory.getDefault();
+ }
+ _socket = ((SSLSocketFactory) sslSocketFactory).createSocket(_socket, _host, _port, true);
+ _input = _socket.getInputStream();
+ _output = _socket.getOutputStream();
+ _usingSecureSocket = true;
+ _tunnelEstablished = true;
+ log.debug("Secure tunnel created");
+ }
+
+
+ /**
+ * Indicates if the connection is completely transparent from end to end.
+ *
+ * @return true if conncetion is not proxied or tunneled through a transparent
+ * proxy; false otherwise.
+ */
+ public boolean isTransparent() {
+ return !isProxied() || _tunnelEstablished;
+ }
+
+ /**
* Return a {@link RequestOutputStream} suitable for writing (possibly
* chunked) bytes to my {@link OutputStream}.
*
@@ -404,6 +458,19 @@
}
}
+ /**
+ * Write the specified bytes to my output stream. The general contract for
+ * write(b, off, len) is that some of the bytes in the array b are written
+ * to the output stream in order; element b[off] is the first byte written
+ * and b[off+len-1] is the last byte written by this operation.
+ *
+ * @param data array containing the data to be written.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ */
+ public void write(byte[] data, int off, int len) throws IOException {
+ _output.write(data, off, len);
+ }
/**
* Write the specified bytes, followed by "\r\n".getBytes() to my
@@ -596,6 +663,8 @@
}
_socket = null;
_open = false;
+ _tunnelEstablished = false;
+ _usingSecureSocket = false;
}
/**
@@ -648,5 +717,10 @@
private static final byte[] CRLF = "\r\n".getBytes();
/** SO_TIMEOUT value */
private int _so_timeout = 0;
-
+ /** An alternative factory for SSL sockets to use */
+ private SocketFactory sslSocketFactory = null;
+ /** Whether or not the _socket is a secure one. Note the difference to _ssl */
+ private boolean _usingSecureSocket = false;
+ /** Whether I am tunneling a proxy or not */
+ private boolean _tunnelEstablished = false;
}
Index: org/apache/commons/httpclient/HttpMethod.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethod.java,v
retrieving revision 1.12
diff -u -w -r1.12 HttpMethod.java
Index: org/apache/commons/httpclient/HttpMethodBase.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
retrieving revision 1.31
diff -u -w -r1.31 HttpMethodBase.java
--- org/apache/commons/httpclient/HttpMethodBase.java 13 Jul 2002 09:06:29 -0000 1.31
+++ org/apache/commons/httpclient/HttpMethodBase.java 17 Jul 2002 15:09:16 -0000
@@ -269,7 +269,6 @@
* Remove the request header associated with the given name.
* Note that header-name matching is case insensitive.
* @param headerName the header name
- * @return the header
*/
public void removeRequestHeader(String headerName) {
requestHeaders.remove(headerName.toLowerCase());
@@ -452,6 +451,7 @@
Set visited = new HashSet();
Set realms = new HashSet();
+ Set proxyRealms = new HashSet();
int retryCount = 0;
for(;;) {
visited.add(connection.getHost() + ":" + connection.getPort() + "|" + HttpMethodBase.generateRequestLine(connection, getName(),getPath(),getQueryString(),(http11 ? "HTTP/1.1" : "HTTP/1.0")));
@@ -493,10 +493,14 @@
}
if (!http11) {
+ if (getName().equals(ConnectMethod.NAME) && (statusCode == HttpStatus.SC_OK)) {
+ log.debug("HttpMethodBase.execute(): leaving connection open for tunneling");
+ } else {
if (log.isDebugEnabled()) {
log.debug("HttpMethodBase.execute(): closing connection since we're using HTTP/1.0");
}
connection.close();
+ }
} else {
Header connectionHeader = getResponseHeader("connection");
if (null != connectionHeader && "close".equalsIgnoreCase(connectionHeader.getValue())) {
@@ -507,22 +511,45 @@
}
}
- if (HttpStatus.SC_UNAUTHORIZED == statusCode) {
- Header wwwauth = getResponseHeader("WWW-Authenticate");
+ if ((HttpStatus.SC_UNAUTHORIZED == statusCode)
+ || (HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED == statusCode)) {
+
+ Header wwwauth = null;
+ Set realmsUsed = null;
+ switch (statusCode) {
+ case HttpStatus.SC_UNAUTHORIZED:
+ wwwauth = getResponseHeader(Authenticator.WWW_AUTH);
+ realmsUsed = realms;
+ break;
+
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ wwwauth = getResponseHeader(Authenticator.PROXY_AUTH);
+ realmsUsed = proxyRealms;
+ break;
+ }
if (null != wwwauth) {
String pathAndCreds = getPath() + ":" + wwwauth.getValue();
- if (realms.contains(pathAndCreds)) {
+ if (realmsUsed.contains(pathAndCreds)) {
if (log.isInfoEnabled()) {
log.info("Already tried to authenticate to \"" + wwwauth.getValue() + "\" but still receiving " + HttpStatus.SC_UNAUTHORIZED + ".");
}
break;
} else {
- realms.add(pathAndCreds);
+ realmsUsed.add(pathAndCreds);
}
boolean authenticated = false;
try {
+ switch (statusCode) {
+ case HttpStatus.SC_UNAUTHORIZED:
authenticated = Authenticator.authenticate(this,state);
+ break;
+
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ authenticated = Authenticator.authenticateProxy(this,state);
+ break;
+ }
+
} catch (HttpException httpe) {
log.warn(httpe.getMessage());
} catch (UnsupportedOperationException uoe) {
@@ -784,6 +811,7 @@
addHostRequestHeader(state,conn);
addCookieRequestHeader(state,conn);
addAuthorizationRequestHeader(state,conn);
+ addProxyAuthorizationRequestHeader(state, conn);
addContentLengthRequestHeader(state,conn);
}
@@ -841,8 +869,8 @@
*/
protected void addAuthorizationRequestHeader(HttpState state, HttpConnection conn) throws IOException, HttpException {
// add authorization header, if needed
- if (!requestHeaders.containsKey("authorization")) {
- Header wwwAuthenticateHeader = (Header)(responseHeaders.get("www-authenticate"));
+ if (!requestHeaders.containsKey(Authenticator.WWW_AUTH_RESP.toLowerCase())) {
+ Header wwwAuthenticateHeader = (Header)(responseHeaders.get(Authenticator.WWW_AUTH.toLowerCase()));
if (null != wwwAuthenticateHeader) {
try {
Authenticator.authenticate(this,state);
@@ -854,6 +882,25 @@
}
/**
+ * Adds a Proxy-Authorization request if needed,
+ * as long as no Proxy-Authorization request header
+ * already exists.
+ */
+ protected void addProxyAuthorizationRequestHeader(HttpState state, HttpConnection conn) throws IOException, HttpException {
+ // add authorization header, if needed
+ if (!requestHeaders.containsKey(Authenticator.PROXY_AUTH_RESP.toLowerCase())) {
+ Header wwwAuthenticateHeader = (Header)(responseHeaders.get(Authenticator.PROXY_AUTH.toLowerCase()));
+ if (null != wwwAuthenticateHeader) {
+ try {
+ Authenticator.authenticateProxy(this,state);
+ } catch(HttpException e) {
+ // ignored
+ }
+ }
+ }
+ }
+
+ /**
* Adds a Content-Length or
* Transer-Encoding: Chunked request header,
* as long as no Content-Length request header
@@ -1197,13 +1244,14 @@
if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
expectedLength = -1;
}
- } else if(canResponseHaveBody(statusCode)){
+ } else if(canResponseHaveBody(statusCode) && !getName().equals(ConnectMethod.NAME)){
/*
* According to the specification, a response with neither Content-Length
* nor Transfer-Encoding indicates that the response has no body. In
* the real world, this usually means that the server just didn't know
* the content-length when it sent the response. FIXME: Should we do
* this only in non-strict mode?
+ * If we do this we will hang forever waiting for a body!
*/
expectedLength = -1;
}
@@ -1326,7 +1374,7 @@
buf.append(qString);
}
- if (!connection.isProxied()) {
+ if (!connection.isProxied() || connection.isTransparent()) {
return (name + " " + buf.toString() + " " + protocol + "\r\n");
} else {
if (connection.isSecure()) {
Index: org/apache/commons/httpclient/methods/GetMethod.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/GetMethod.java,v
retrieving revision 1.10
diff -u -w -r1.10 GetMethod.java
--- org/apache/commons/httpclient/methods/GetMethod.java 29 Apr 2002 16:04:05 -0000 1.10
+++ org/apache/commons/httpclient/methods/GetMethod.java 17 Jul 2002 15:09:16 -0000
@@ -190,9 +190,10 @@
/**
- * Use disk setter.
+ * Buffer the response in a file or not. The default is false.
*
- * @param useDisk New value of useDisk
+ * @param useDisk If true the entire response will be buffered in a
+ * temporary file.
*/
public void setUseDisk(boolean useDisk) {
checkNotUsed();
@@ -201,9 +202,10 @@
/**
- * Use disk getter.
+ * Tells if the response will be buffered in a file.
*
- * @param boolean useDisk value
+ * @param boolean If true the entire response will be buffered in a
+ * temporary file.
*/
public boolean getUseDisk() {
return useDisk;
Index: org/apache/commons/httpclient/methods/PostMethod.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v
retrieving revision 1.11
diff -u -w -r1.11 PostMethod.java
--- org/apache/commons/httpclient/methods/PostMethod.java 10 Jul 2002 11:33:29 -0000 1.11
+++ org/apache/commons/httpclient/methods/PostMethod.java 17 Jul 2002 15:09:17 -0000
@@ -73,6 +73,9 @@
import org.apache.commons.httpclient.log.LogSource;
import java.util.Vector;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
@@ -99,7 +102,18 @@
* @author Doug Sale
*/
public class PostMethod extends GetMethod {
+ /**
+ * The content length will be calculated automatically. This implies buffering
+ * of the content.
+ */
+ public static final int CONTENT_LENGTH_AUTO = -2;
+ /**
+ * The request will use chunked transfer encoding. Content length is not
+ * calculated and the content is not buffered.
+ * Note: Chunked requests are not supported at the moment.
+ */
+ public static final int CONTENT_LENGTH_CHUNKED = -1;
// ----------------------------------------------------------- Constructors
@@ -108,6 +122,7 @@
*/
public PostMethod() {
super();
+ setFollowRedirects(false);
}
/**
@@ -116,6 +131,7 @@
*/
public PostMethod(String path) {
super(path);
+ setFollowRedirects(false);
}
/**
@@ -125,6 +141,7 @@
*/
public PostMethod(String path, String tempDir) {
super(path, tempDir);
+ setFollowRedirects(false);
}
/**
@@ -135,6 +152,7 @@
*/
public PostMethod(String path, String tempDir, String tempFile) {
super(path, tempDir, tempFile);
+ setFollowRedirects(false);
}
@@ -149,12 +167,26 @@
}
/**
+ * A POST request can only be redirected if input is buffered.
+ * Overrides method of {@link HttpMethodBase}.
+ * @return true if request is buffered and setFollowRedirects
+ * was set to true.
+ */
+ public boolean getFollowRedirects() {
+ if (!super.getFollowRedirects()) return false;
+ return (buffer == null);
+ }
+
+
+ /**
* Override method of {@link HttpMethodBase}
* to clear my request body.
*/
public void recycle() {
super.recycle();
requestBody = null;
+ requestContentLength = CONTENT_LENGTH_AUTO;
+ buffer = null;
parameters.clear();
}
@@ -174,6 +206,30 @@
}
/**
+ * Sets length information about the request body.
+ *
Note: If you specify a content length the request is unbuffered. This + * prevents automatic retry if a request fails the first time. This means + * that the HttpClient can not perform authorization automatically but will + * throw an Exception. You will have to set the necessary 'Authorization' or + * 'Proxy-Authorization' headers manually.
+ * + * @param length size in bytes or any of CONTENT_LENGTH_AUTO, + * CONTENT_LENGTH_CHUNKED. If number of bytes is specified the content will + * not be buffered internally and the Content-Length header of the request + * will be used. In this case the user is responsible to supply the correct + * content length. + * + * + * + */ + public void setRequestContentLength(int length) { + if ((length == CONTENT_LENGTH_CHUNKED) && !isHttp11()) { + throw new RuntimeException("Chunked transfer encoding not allowed for HTTP/1.0"); + } + requestContentLength = length; + } + + /** * Add a new parameter to be used in the POST request body. * * @param paramName The parameter name to add. @@ -238,6 +294,7 @@ /** * Gets the parameter of the specified name. + * * If there exists more than one parameter with the name paramName, * then only the first one is returned. * @@ -261,6 +318,7 @@ /** * Gets the parameters currently added to the PostMethod. + * * If there are no parameters, a valid array is returned with * zero elements. * The returned array object contains an array of pointers to @@ -345,11 +403,20 @@ * cannot be altered until I am {@link #recycle recycled}. * * @throws IllegalStateException if request params have been added + * @deprecated This method converts characters to bytes in a platform dependent + * encoding. Use setRequestBody(InputStream) instead. */ public void setRequestBody(String body) { if(!parameters.isEmpty()) { throw new IllegalStateException("Request parameters have already been added."); } + requestBody = new ByteArrayInputStream(body.getBytes()); + } + + public void setRequestBody(InputStream body) { + if(!parameters.isEmpty()) { + throw new IllegalStateException("Request parameters have already been added."); + } requestBody = body; } @@ -360,7 +427,7 @@ * request body from the paramters if they exist. Null otherwise. * @since 2.0 */ - public String getRequestBody() { + public InputStream getRequestBody() { if(requestBody != null){ return requestBody; }else if (!parameters.isEmpty()){ @@ -386,6 +453,9 @@ * Override method of {@link HttpMethodBase} * to write request parameters as the request body. * + * The input stream will be truncated after the specified content length. + * @throws IOException if the stream ends before the specified content length. + * *Once this method has been invoked, the request parameters * cannot be altered until I am {@link #recycle recycled}. * @@ -396,7 +466,31 @@ if(null == requestBody) { requestBody = generateRequestBody(parameters); } - conn.print(requestBody); + if ((repeatCount > 0) && (buffer == null)) { + throw new HttpException("Sorry, unbuffered POST request can not be repeated."); + } + repeatCount++; + + byte[] data = new byte[10000]; + int l = requestBody.read(data); + int total = 0; + while (l > 0) { + if ((requestContentLength > 0) && (total + l > requestContentLength)) { + l = requestContentLength - total; + conn.write(data, 0, l); + break; + } + conn.write(data, 0, l); + total += l; + l = requestBody.read(data); + } + if ((requestContentLength > 0) && (total < requestContentLength)) { + throw new IOException("unexpected end of input stream"); + } + if (buffer != null) { + //restore buffered content for repeated requests + requestBody = new ByteArrayInputStream(buffer.toByteArray()); + } return true; } @@ -409,10 +503,41 @@ protected int getRequestContentLength() { if(null == requestBody) { requestBody = generateRequestBody(parameters); + bufferContent(); + } + + if (requestContentLength != CONTENT_LENGTH_AUTO) { + return requestContentLength; } - return requestBody.getBytes().length; + + bufferContent(); + + return requestContentLength; } + /** + * Buffers the request body and calculates the content length. + * If the method was called earlier it returns immediately. + */ + private void bufferContent() { + if (buffer != null) return; + try { + buffer = new ByteArrayOutputStream(); + byte[] data = new byte[10000]; + int l = requestBody.read(data); + int total = 0; + while (l > 0) { + buffer.write(data, 0, l); + total += l; + l = requestBody.read(data); + } + requestBody = new ByteArrayInputStream(buffer.toByteArray()); + requestContentLength = total; + } catch(IOException e) { + requestBody = null; + requestContentLength = 0; + } + } // -------------------------------------------------------------- Class Methods @@ -422,7 +547,7 @@ * TODO: consider moving this out into URIUtil. * @return urlencoded string */ - static String generateRequestBody(List params) { + static InputStream generateRequestBody(List params) { Iterator it = params.iterator(); StringBuffer sb = new StringBuffer(); while(it.hasNext()) { @@ -435,15 +560,16 @@ sb.append("&"); } } - return sb.toString(); + return new ByteArrayInputStream(sb.toString().getBytes()); } - // -------------------------------------------------------------- Instance Variables - private String requestBody = null; + private InputStream requestBody = null; private Vector parameters = new Vector(); - + private int requestContentLength = CONTENT_LENGTH_AUTO; + private ByteArrayOutputStream buffer = null; + private int repeatCount = 0; // -------------------------------------------------------------- Constants Index: org/apache/commons/httpclient/methods/PutMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v retrieving revision 1.9 diff -u -w -r1.9 PutMethod.java --- org/apache/commons/httpclient/methods/PutMethod.java 25 Apr 2002 20:09:45 -0000 1.9 +++ org/apache/commons/httpclient/methods/PutMethod.java 17 Jul 2002 15:09:17 -0000 @@ -96,6 +96,7 @@ * No-arg constructor. */ public PutMethod() { + setFollowRedirects(false); } @@ -105,6 +106,7 @@ */ public PutMethod(String path) { super(path); + setFollowRedirects(false); }