rwaldhoff 01/08/13 09:01:05 Modified: httpclient/src/java/org/apache/commons/httpclient Tag: rlwrefactoring HttpMethodBase.java httpclient/src/java/org/apache/commons/httpclient/methods Tag: rlwrefactoring OptionsMethod.java PostMethod.java PutMethod.java Log: Several changes completed over the weekend: + changes to processResponseXXX method signatures + moving cookie parsing to processResponseHeaders method + track visited paths to detect HTTP redirect loops + track authentication attempts to detect bad creds + re-enabled POST and PUT request body processing Revision Changes Path No revision No revision 1.10.2.4 +66 -37 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java Index: HttpMethodBase.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.10.2.3 retrieving revision 1.10.2.4 diff -u -r1.10.2.3 -r1.10.2.4 --- HttpMethodBase.java 2001/08/11 00:36:31 1.10.2.3 +++ HttpMethodBase.java 2001/08/13 16:01:05 1.10.2.4 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.10.2.3 2001/08/11 00:36:31 rwaldhoff Exp $ - * $Revision: 1.10.2.3 $ - * $Date: 2001/08/11 00:36:31 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.10.2.4 2001/08/13 16:01:05 rwaldhoff Exp $ + * $Revision: 1.10.2.4 $ + * $Date: 2001/08/13 16:01:05 $ * ==================================================================== * Copyright (C) The Apache Software Foundation. All rights reserved. * @@ -21,6 +21,8 @@ import java.util.List; import java.util.Vector; import java.net.URLEncoder; +import java.util.HashSet; +import java.util.Set; import org.apache.commons.httpclient.log.*; @@ -61,6 +63,7 @@ protected int getRequestContentLength() { return -1; } + protected boolean needExpectation() { return false; } @@ -218,8 +221,14 @@ throw new HttpException("Not valid"); } + Set visited = new HashSet(); + Set realms = new HashSet(); + int repeated = 0; while(repeated++ < 5) { + // for now, just track path + visited.add(getPath()); + log.debug("HttpMethodBase.execute(): looping " + repeated); if(!connection.isOpen()) { log.debug("HttpMethodBase.execute(): opening connection."); @@ -263,24 +272,32 @@ if(HttpStatus.SC_UNAUTHORIZED == statusCode) { Header wwwauth = getResponseHeader("WWW-Authenticate"); if(null != wwwauth) { + String foo = getPath() + ":" + wwwauth.getValue(); + if(realms.contains(foo)) { + if(log.isInfoEnabled()) { + log.info("Already tried to authenticate to \"" + wwwauth.getValue() + "\" but still receiving " + HttpStatus.SC_UNAUTHORIZED + "."); + return HttpStatus.SC_UNAUTHORIZED; + } + } else { + realms.add(foo); + } + if(null == Authenticator.challengeResponse(wwwauth.getValue(),state)) { // won't be able to authenticate to this challenge // without additional information if(log.isDebugEnabled()) { - log.debug("Server demands authentication credentials, but none are available, so aborting."); + log.debug("HttpMethodBase.execute(): Server demands authentication credentials, but none are available, so aborting."); } return HttpStatus.SC_UNAUTHORIZED; } else { if(log.isDebugEnabled()) { - log.debug("Server demands authentication credentials, will try again."); + log.debug("HttpMethodBase.execute(): Server demands authentication credentials, will try again."); } // let's try it again, using the credentials continue; } } - } - - if(HttpStatus.SC_MOVED_TEMPORARILY == statusCode || + } else if(HttpStatus.SC_MOVED_TEMPORARILY == statusCode || HttpStatus.SC_MOVED_PERMANENTLY == statusCode || HttpStatus.SC_TEMPORARY_REDIRECT == statusCode) { if(followRedirects()) { @@ -306,6 +323,9 @@ // or from "http://www.mydomain.com/" // to "https://secure.mydomain.com/" // + + // XXX Should handle query-string changes as well! + Header location = getResponseHeader("location"); if(location != null) { String absolutePath = location.getValue(); @@ -329,14 +349,24 @@ if(log.isDebugEnabled()) { log.debug("Changing path from \"" + getPath() + "\" to \"" + absolutePath + "\" in response to " + statusCode + " response."); } - setPath(absolutePath); - // now let's try it again with the new path - continue; + // if we haven't already, let's try it again with the new path + if(visited.contains(absolutePath)) { + throw new HttpException("Redirect going into a loop, visited \"" + absolutePath + "\" already."); + } else { + setPath(absolutePath); + continue; + } } else { // got a redirect response, but no location header - // log warning? + if(log.isInfoEnabled()) { + log.info("HttpMethodBase.execute(): Received " + statusCode + " response, but no \"Location\" header. Returning " + statusCode + "."); + } return statusCode; } + } else { + // got a redirect response, + // but followRedirects is false + return statusCode; } } else { return statusCode; @@ -359,18 +389,35 @@ protected void readResponse(State state, HttpConnection conn) throws IOException, HttpException { log.debug("HttpMethodBase.readResponse(State,HttpConnection)"); readStatusLine(state,conn); - processStatusLine(); + processStatusLine(state,conn); readResponseHeaders(state,conn); - processResponseHeaders(); + processResponseHeaders(state,conn); readResponseBody(state,conn); - processResponseBody(); + processResponseBody(state,conn); } - protected void processStatusLine() { + protected void processStatusLine(State state, HttpConnection conn) { } - protected void processResponseHeaders() { + + protected void processResponseHeaders(State state, HttpConnection conn) { + // add cookies, if any + // should we set cookies? + Header setCookieHeader = getResponseHeader("set-cookie2"); + if(null == setCookieHeader) { //ignore old-style if new is supported + setCookieHeader = getResponseHeader("set-cookie"); + } + + if(setCookieHeader != null) { + try { + Cookie[] cookies = Cookie.parse(conn.getHost(), setCookieHeader); + state.addCookies(cookies); + } catch (Exception e) { + log.error("processResponseHeaders(State,HttpConnection)",e); + } + } } - protected void processResponseBody() { + + protected void processResponseBody(State state, HttpConnection conn) { } protected void readStatusLine(State state, HttpConnection conn) throws IOException, HttpException { @@ -440,24 +487,6 @@ Header header = new Header(name, value); responseHeaders.put(match, header); } - - // add cookies, if any - // should we set cookies? - Header header = getResponseHeader("set-cookie2"); - // if the server doesn't support new cookies, - // we'll use the old cookies - if (header == null) { - header = getResponseHeader("set-cookie"); - } - - if (header != null) { - try { - Cookie[] cookies = Cookie.parse(conn.getHost(), header); - state.addCookies(cookies); - } catch (Exception e) { - e.printStackTrace(); - } - } } protected void readResponseBody(State state, HttpConnection conn) throws IOException, HttpException { @@ -563,7 +592,7 @@ setRequestHeader(HttpClient.USER_AGENT); } - // add host (should do this conditionally?) + // add host (should do this conditionally?, i.e., don't send to http/1.0?) if (!requestHeaders.containsKey("host")) { setRequestHeader("Host",conn.getHost()); } No revision No revision 1.2.2.2 +5 -4 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/OptionsMethod.java Index: OptionsMethod.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/OptionsMethod.java,v retrieving revision 1.2.2.1 retrieving revision 1.2.2.2 diff -u -r1.2.2.1 -r1.2.2.2 --- OptionsMethod.java 2001/08/10 22:27:12 1.2.2.1 +++ OptionsMethod.java 2001/08/13 16:01:05 1.2.2.2 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/OptionsMethod.java,v 1.2.2.1 2001/08/10 22:27:12 rwaldhoff Exp $ - * $Revision: 1.2.2.1 $ - * $Date: 2001/08/10 22:27:12 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/OptionsMethod.java,v 1.2.2.2 2001/08/13 16:01:05 rwaldhoff Exp $ + * $Revision: 1.2.2.2 $ + * $Date: 2001/08/13 16:01:05 $ * * ==================================================================== * @@ -69,6 +69,7 @@ import org.apache.commons.httpclient.State; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.HttpConnection; /** @@ -137,7 +138,7 @@ // ----------------------------------------------------- HttpMethod Methods - protected void processResponseHeaders() { + protected void processResponseHeaders(State state, HttpConnection conn) { Header allowHeader = getResponseHeader("allow"); if (allowHeader != null) { String allowHeaderValue = allowHeader.getValue(); 1.3.2.2 +108 -87 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java Index: PostMethod.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v retrieving revision 1.3.2.1 retrieving revision 1.3.2.2 diff -u -r1.3.2.1 -r1.3.2.2 --- PostMethod.java 2001/08/10 22:27:12 1.3.2.1 +++ PostMethod.java 2001/08/13 16:01:05 1.3.2.2 @@ -1,64 +1,13 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v 1.3.2.1 2001/08/10 22:27:12 rwaldhoff Exp $ - * $Revision: 1.3.2.1 $ - * $Date: 2001/08/10 22:27:12 $ - * - * ==================================================================== - * - * The Apache Software License, Version 1.1 - * - * Copyright (c) 1999 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, if - * any, must include the following acknowlegement: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowlegement may appear in the software itself, - * if and wherever such third-party acknowlegements normally appear. - * - * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software - * Foundation" must not be used to endorse or promote products derived - * from this software without prior written permission. For written - * permission, please contact [EMAIL PROTECTED] - * - * 5. Products derived from this software may not be called "Apache" - * nor may "Apache" appear in their names without prior written - * permission of the Apache Group. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v 1.3.2.2 2001/08/13 16:01:05 rwaldhoff Exp $ + * $Revision: 1.3.2.2 $ + * $Date: 2001/08/13 16:01:05 $ * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * <http://www.apache.org/>. + * Copyright (C) The Apache Software Foundation. All rights reserved. * - * [Additional notices, if required by prior licensing conditions] - * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. */ package org.apache.commons.httpclient.methods; @@ -66,10 +15,13 @@ import java.io.*; import java.util.*; import java.net.URLEncoder; -import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.State; -import org.apache.commons.httpclient.Header; - +import org.apache.commons.httpclient.HttpConnection; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.HttpException; +import java.util.Iterator; +import java.util.HashMap; +import java.util.List; /** * POST Method. @@ -77,8 +29,7 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Remy Maucherat</a> * @author <a href="mailto:[EMAIL PROTECTED]">Doug Sale</a> */ -public class PostMethod - extends GetMethod { +public class PostMethod extends GetMethod { // ----------------------------------------------------------- Constructors @@ -125,39 +76,108 @@ String tempFile) { super(path, useDisk, tempDir, tempFile); } - - - // ----------------------------------------------------- Instance Variables - - /** - * Parameters hashtable. - */ - Hashtable parameters = new Hashtable(); - - - // --------------------------------------------------------- Public Methods + // ----------------------------------------------------- HttpMethod Methods public String getName() { return "POST"; } - /** - * Add parameter. - */ - public void addParameter(String name, String value) { - checkNotUsed(); - parameters.put(name, value); - } - - - // ----------------------------------------------------- HttpMethod Methods - - public void recycle() { super.recycle(); - parameters.clear(); + requestBody = null; + } + + public void setParameter(String parameterName, String parameterValue) { + if(null != requestBody) { + throw new IllegalStateException("Request body already generated."); + } + super.setParameter(parameterName,parameterValue); + } + + public void addParameter(String parameterName, String parameterValue) { + if(null != requestBody) { + throw new IllegalStateException("Request body already generated."); + } + super.addParameter(parameterName,parameterValue); + } + + public void removeParameter(String paramName) { + if(null != requestBody) { + throw new IllegalStateException("Request body already generated."); + } + super.removeParameter(paramName); + } + + public void removeParameter(String paramName, String paramValue) { + if(null != requestBody) { + throw new IllegalStateException("Request body already generated."); + } + super.removeParameter(paramName,paramValue); + } + + protected void writeRequestLine(State state, HttpConnection conn) throws IOException, HttpException { + // don't send parameters on query string + log.debug("PostMethod.writeRequestLine(State,HttpConnection)"); + String requestLine = HttpMethodBase.generateRequestLine(conn, getName(),getPath(),queryString,null,(http11 ? "HTTP/1.1" : "HTTP/1.0")); + conn.print(requestLine); + } + + protected void generateRequestHeaders(State state, HttpConnection conn) throws IOException, HttpException { + super.generateRequestHeaders(state,conn); + if(!parameters.isEmpty()) { + setRequestHeader("Content-Type","application/x-www-form-urlencoded"); + } + } + + protected void writeRequestBody(State state, HttpConnection conn) throws IOException, HttpException { + if(null == requestBody) { + requestBody = generateRequestBody(parameters); + } + conn.print(requestBody); + bodySent = true; + } + + protected int getRequestContentLength() { + if(null == requestBody) { + requestBody = generateRequestBody(parameters); + } + return requestBody.length(); + } + + protected String generateRequestBody(HashMap params) { + if (!params.isEmpty()) { + StringBuffer sb = new StringBuffer(); + Iterator it = parameters.keySet().iterator(); + while(it.hasNext()) { + String name = (String)(it.next()); + Object value = parameters.get(name); + if(value instanceof List) { + List list = (List)value; + Iterator valit = list.iterator(); + while(valit.hasNext()) { + if(sb.length() > 0) { sb.append("&"); } + sb.append(URLEncoder.encode(name)); + Object val2 = valit.next(); + if(null != val2) { + sb.append("="); + sb.append(URLEncoder.encode(String.valueOf(val2))); + } + } + } else { + if(sb.length() > 0) { sb.append("&"); } + sb.append(URLEncoder.encode(name)); + if(null != value) { + sb.append("="); + sb.append(URLEncoder.encode(String.valueOf(value))); + } + } + } + return sb.toString(); + } else { + return ""; + } } /** @@ -172,4 +192,5 @@ return true; } + protected String requestBody = null; } 1.3.2.2 +78 -86 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java Index: PutMethod.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v retrieving revision 1.3.2.1 retrieving revision 1.3.2.2 diff -u -r1.3.2.1 -r1.3.2.2 --- PutMethod.java 2001/08/10 22:27:12 1.3.2.1 +++ PutMethod.java 2001/08/13 16:01:05 1.3.2.2 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v 1.3.2.1 2001/08/10 22:27:12 rwaldhoff Exp $ - * $Revision: 1.3.2.1 $ - * $Date: 2001/08/10 22:27:12 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v 1.3.2.2 2001/08/13 16:01:05 rwaldhoff Exp $ + * $Revision: 1.3.2.2 $ + * $Date: 2001/08/13 16:01:05 $ * * Copyright (C) The Apache Software Foundation. All rights reserved. * @@ -19,14 +19,15 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; import java.net.URL; import java.net.URLConnection; import org.apache.commons.httpclient.HttpConnection; +import org.apache.commons.httpclient.RequestOutputStream; import org.apache.commons.httpclient.State; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpMethodBase; -import org.apache.commons.httpclient.log.Log; -import org.apache.commons.httpclient.log.LogSource; +import org.apache.commons.httpclient.HttpException; /** @@ -85,19 +86,34 @@ /** * Send the contents of a file. + * @deprecated */ - public void sendData(File file) - throws IOException { + public void sendData(File file) throws IOException { + setData(file); + } + + /** + * Send the contents of a file. + * @deprecated + */ + public void setData(File file) throws IOException { checkNotUsed(); this.file = file; } + /** + * Send the contents of the resource at the specified URL. + * @deprecated + */ + public void sendData(URL url) throws IOException { + sendData(url); + } /** * Send the contents of the resource at the specified URL. + * @deprecated */ - public void sendData(URL url) - throws IOException { + public void setData(URL url) throws IOException { checkNotUsed(); this.url = url; } @@ -105,29 +121,52 @@ /** * Send the contents of a byte array. + * @deprecated */ public void sendData(byte[] data) { + setData(data); + } + + /** + * Send the contents of a byte array. + */ + public void setData(byte[] data) { checkNotUsed(); this.data = data; } - /** * Send the contents of a string. + * @deprecated */ public void sendData(String data) { + setData(data); + } + + /** + * Send the contents of a string. + */ + public void setData(String data) { checkNotUsed(); sendData(data.getBytes()); } + /** + * Send the contents of an input stream. The contents will be buffered into + * memory. To upload large entities, it is recommended to first buffer the + * data into a temporary file, and then send that file. + * @deprecated + */ + public void sendData(InputStream is) throws IOException { + setData(is); + } /** * Send the contents of an input stream. The contents will be buffered into * memory. To upload large entities, it is recommended to first buffer the * data into a temporary file, and then send that file. */ - public void sendData(InputStream is) - throws IOException { + public void setData(InputStream is) throws IOException { checkNotUsed(); byte[] buffer = new byte[4096]; ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -144,66 +183,23 @@ // ----------------------------------------------------- HttpMethod Methods - - - protected void writeRequestBody(State state, HttpConnection conn) { - // XXX IMPLEMENT ME XXX - } - - /** - * Is the query body submitted through an InputStream of with a String. - * If an InputStream is available, it's used. - * - * @return boolean True if the content is avalable in an InputStream - */ - public boolean isStreamedQuery() { - return ((file != null) || (url != null)); - } - - /** - * Recycle the method object, so that it can be reused again. Any attempt - * to reuse an object without recycling it will throw a WebdavException. - */ - public void recycle() { - super.recycle(); - data = null; - url = null; - file = null; - } - - - /** - * Generate the query body. - * - * @return String query - */ -/* - public String generateQuery() { - if (query != null){ - return query; - } - if (data == null) { - return ""; - } else { - return new String(data); + protected void writeRequestBody(State state, HttpConnection conn) throws IOException, HttpException { + RequestOutputStream out = conn.getRequestOutputStream(); + if((http11) && (getRequestHeader("Content-Length") == null)) { + out.setUseChunking(true); } - } -*/ - - /** - * Stream the body of the query. This function should be used to send large - * request bodies. - */ -/* - public void streamQuery(OutputStream out) - throws IOException { InputStream inputStream = null; if (file != null) { inputStream = new FileInputStream(file); } else if (url != null) { inputStream = url.openConnection().getInputStream(); + } else if(data != null){ + inputStream = new ByteArrayInputStream(data); + } else { + bodySent = true; + return; } byte[] buffer = new byte[4096]; @@ -213,37 +209,33 @@ if (nb == -1) { break; } - if(wireLog.isInfoEnabled()) { - wireLog.info(">> \"" + new String(buffer) + "\""); - } out.write(buffer, 0, nb); } - inputStream.close(); + out.close(); + bodySent = true; + } + protected int getRequestContentLength() { + if(null != data) { + return data.length; + } else if(null != file) { + return (int)(file.length()); + } else if(url != null) { + return -1; + } else { + return 0; + } } -*/ - /** - * Parse response. - * - * @param is Input stream - */ -/* - public void parseResponse(InputStream is) - throws IOException { + public void recycle() { + super.recycle(); + data = null; + url = null; + file = null; } -*/ - /** - * Return true if the method should ask for an expectation. - * - * @return true if an expectation will be sent - */ public boolean needExpectation() { return true; } - - - static protected final Log wireLog = LogSource.getInstance("httpclient.wire"); }