From: Brad C. <bc...@bo...> - 2003-08-13 00:17:51
|
RFE: http://sourceforge.net/tracker/index.php?func=detail&aid=625328&group_id=47038&atid=448269 I have managed to get an HtmlFileInput to upload a file to the server with this patch. It's likely in no condition to go into just CVS yet, I'm just looking for comments and suggestions for improvement. My personal issues with it are: - I really have no clue what I'm doing :) - It requires the unreleased commons-httpclient 2.0 (this is probably unavoidable) - I've really done nothing to see how this mimics real world web browsers, I just know it worked for me - FormEncodingType seems sort of like a waste to me, but it seemed "right" to model it after SubmitMethod - FormEncodingType naming conventions were basically random. Someone with more experience with this could probably name things better. - I had to add a lot of new API to preserve compatibility because so many things are public. Maybe some of this could be removed?? - KeyDataPair seems like a really bad idea to me, but it helped to not have to touch much of the surrounding code - I really have no idea how to "properly" unit test this, so there are no new tests (though all the old ones still pass) - The new stuff is completely unimplemented in FakeWebConnection - There were other little things I noticed in the RFCs that I just ignored because I don't know exactly how commons-httpclient fits in - The checkstyle task in the ant build.xml won't work so I doubt this meets the standards (though I did make an effort) - The copyright is not on the new files, but only to make the diff smaller. Just act like it's there :) - If the encoding type on the form is not correct it defaults to the old broken behavior. From the RFCs it sounds like there should be a MIME encoding of the file and it added to the POST as text. I could live with this being ignored but maybe others can't? Brad C Index: src/java/com/gargoylesoftware/htmlunit/FormEncodingType.java =================================================================== RCS file: src/java/com/gargoylesoftware/htmlunit/FormEncodingType.java diff -N src/java/com/gargoylesoftware/htmlunit/FormEncodingType.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/com/gargoylesoftware/htmlunit/FormEncodingType.java 12 Aug 2003 22:56:55 -0000 @@ -0,0 +1,73 @@ +package com.gargoylesoftware.htmlunit; + +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.MultipartPostMethod; + +/** + * A collection of constants that represent the various ways a form can be + * encoded when submitted + * + * @version $Revision: 1.1 $ + */ +public class FormEncodingType { + + /** + * URL_ENCODED + */ + public static final FormEncodingType URL_ENCODED = + new FormEncodingType(PostMethod.FORM_URL_ENCODED_CONTENT_TYPE); + /** + * MULTIPART + */ + public static final FormEncodingType MULTIPART = + new FormEncodingType(MultipartPostMethod.MULTIPART_FORM_CONTENT_TYPE); + + private final String name_; + + private FormEncodingType(final String name) { + name_ = name; + } + + /** + * Return the name of this EncodingType + * + * @return See above + */ + public String getName() { + return name_; + } + + /** + * Return the constant that matches the given name + * + * @param name The name to search by + * @return See above + */ + public static FormEncodingType getInstance(final String name) { + final String lowerCaseName = name.toLowerCase(); + final FormEncodingType allInstances[] = new FormEncodingType[] { URL_ENCODED, MULTIPART }; + + int i; + for (i = 0; i < allInstances.length; i++) { + if (allInstances[i].getName().equals(lowerCaseName)) { + return allInstances[i]; + } + } + + // Special case: empty string defaults to url encoded + if (name.equals("")) { + return URL_ENCODED; + } + + throw new IllegalArgumentException("No encoding type found for [" + name + "]"); + } + + /** + * Return a string representation of this object + * + * @return See above + */ + public String toString() { + return "EncodingType[name=" + getName() + "]"; + } +} Index: src/java/com/gargoylesoftware/htmlunit/HttpWebConnection.java =================================================================== RCS file: /cvsroot/htmlunit/htmlunit/src/java/com/gargoylesoftware/htmlunit/HttpWebConnection.java,v retrieving revision 1.13 diff -u -r1.13 HttpWebConnection.java --- src/java/com/gargoylesoftware/htmlunit/HttpWebConnection.java 19 Jul 2003 17:17:30 -0000 1.13 +++ src/java/com/gargoylesoftware/htmlunit/HttpWebConnection.java 12 Aug 2003 22:56:55 -0000 @@ -37,6 +37,7 @@ */ package com.gargoylesoftware.htmlunit; +import java.io.File; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -58,6 +59,7 @@ import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.MultipartPostMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.logging.Log; @@ -98,12 +100,32 @@ super(webClient, proxyHost, proxyPort); } - + /** + * Submit a request and retrieve a response + * + * @param parameters Any parameters + * @param url The url of the server + * @param submitMethod The submit method. Ie SubmitMethod.GET + * @param requestHeaders Any headers that need to be put in the request. + * @return See above + * @exception IOException If an IO error occurs + */ + public WebResponse getResponse( + final URL url, + final SubmitMethod submitMethod, + final List parameters, + final Map requestHeaders ) + throws + IOException { + return this.getResponse(url, FormEncodingType.URL_ENCODED, submitMethod, parameters, requestHeaders); + } + /** * Submit a request and retrieve a response * * @param parameters Any parameters * @param url The url of the server + * @param encType Encoding type of the form when done as a POST * @param submitMethod The submit method. Ie SubmitMethod.GET * @param requestHeaders Any headers that need to be put in the request. * @return See above @@ -111,6 +133,7 @@ */ public WebResponse getResponse( final URL url, + final FormEncodingType encType, final SubmitMethod submitMethod, final List parameters, final Map requestHeaders ) @@ -122,14 +145,14 @@ try { long startTime, endTime; - HttpMethod httpMethod = makeHttpMethod( url, submitMethod, parameters, requestHeaders ); + HttpMethod httpMethod = makeHttpMethod( url, encType, submitMethod, parameters, requestHeaders ); startTime = System.currentTimeMillis(); int responseCode = httpClient.executeMethod( httpMethod ); endTime = System.currentTimeMillis(); if( responseCode == 401 ) { // Authentication required final KeyValuePair pair = getCredentials( httpMethod, url ); if( pair != null ) { - httpMethod = makeHttpMethod( url, submitMethod, parameters, requestHeaders ); + httpMethod = makeHttpMethod( url, encType, submitMethod, parameters, requestHeaders ); addCredentialsToHttpMethod( httpMethod, pair ); startTime = System.currentTimeMillis(); responseCode = httpClient.executeMethod( httpMethod ); @@ -200,6 +223,7 @@ private HttpMethod makeHttpMethod( final URL url, + final FormEncodingType encType, final SubmitMethod method, final List parameters, final Map requestHeaders ) @@ -224,7 +248,11 @@ } } else if( method == SubmitMethod.POST ) { - httpMethod = new PostMethod( path ); + if (encType == FormEncodingType.URL_ENCODED) { + httpMethod = new PostMethod( path ); + } else { + httpMethod = new MultipartPostMethod(path); + } final String queryString = url.getQuery(); if( queryString != null ) { httpMethod.setQueryString(queryString); @@ -234,16 +262,29 @@ // Note that this has to be done in two loops otherwise it won't // be able to support two elements with the same name. iterator = parameters.iterator(); - while( iterator.hasNext() ) { - final NameValuePair pair = ( NameValuePair )iterator.next(); - ( ( PostMethod )httpMethod ).removeParameter( pair.getName(), pair.getValue() ); - } - - iterator = parameters.iterator(); - while( iterator.hasNext() ) { - final NameValuePair pair = ( NameValuePair )iterator.next(); - ( ( PostMethod )httpMethod ).addParameter( pair.getName(), pair.getValue() ); - } + if (encType == FormEncodingType.URL_ENCODED) { + while( iterator.hasNext() ) { + final NameValuePair pair = ( NameValuePair )iterator.next(); + ( ( PostMethod )httpMethod ).removeParameter( pair.getName(), pair.getValue() ); + } + + iterator = parameters.iterator(); + while( iterator.hasNext() ) { + final NameValuePair pair = ( NameValuePair )iterator.next(); + ( ( PostMethod )httpMethod ).addParameter( pair.getName(), pair.getValue() ); + } + } else { + iterator = parameters.iterator(); + while (iterator.hasNext()) { + final KeyValuePair pair = (KeyValuePair) iterator.next(); + if (pair instanceof KeyDataPair) { + File f = (File) ((KeyDataPair)pair).getData(); + ((MultipartPostMethod) httpMethod).addParameter(pair.getName(), f); + } else { + ((MultipartPostMethod) httpMethod).addParameter(pair.getName(), pair.getValue()); + } + } + } } else { throw new IllegalStateException( "Submit method not yet supported: " + method ); Index: src/java/com/gargoylesoftware/htmlunit/KeyDataPair.java =================================================================== RCS file: src/java/com/gargoylesoftware/htmlunit/KeyDataPair.java diff -N src/java/com/gargoylesoftware/htmlunit/KeyDataPair.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/com/gargoylesoftware/htmlunit/KeyDataPair.java 12 Aug 2003 22:56:55 -0000 @@ -0,0 +1,20 @@ +package com.gargoylesoftware.htmlunit; + +public class KeyDataPair extends KeyValuePair { + + private Object dataObject; + + public KeyDataPair(String key, String value) { + super(key, value); + } + + public KeyDataPair(String key, Object data) { + super(key, data.toString()); + this.dataObject = data; + } + + public Object getData() { + return this.dataObject; + } + +} Index: src/java/com/gargoylesoftware/htmlunit/WebClient.java =================================================================== RCS file: /cvsroot/htmlunit/htmlunit/src/java/com/gargoylesoftware/htmlunit/WebClient.java,v retrieving revision 1.45 diff -u -r1.45 WebClient.java --- src/java/com/gargoylesoftware/htmlunit/WebClient.java 16 Jul 2003 20:11:21 -0000 1.45 +++ src/java/com/gargoylesoftware/htmlunit/WebClient.java 12 Aug 2003 22:56:55 -0000 @@ -275,8 +275,64 @@ FailingHttpStatusCodeException { return getPage(webWindow, url, method, parameters, getThrowExceptionOnFailingStatusCode()); } - - + /** + * Return a page. + * + * @param webWindow The window that the new page will be loaded into. + * @param url The url of the server + * @param encType Encoding type of the form when done as a POST + * @param method The submit method. Ie Submit.GET or SubmitMethod.POST + * @param parameters A list of {@link + * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that + * contain the parameters to send to the server + * @return The page that was loaded. + * @exception IOException If an IO error occurs + * @exception FailingHttpStatusCodeException If the server returns a + * failing status code AND the property + * "throwExceptionOnFailingStatusCode" is set to true (see {@link + * #setThrowExceptionOnFailingStatusCode(boolean)}) + */ + public Page getPage( + final WebWindow webWindow, + final URL url, + final FormEncodingType encType, + final SubmitMethod method, + final List parameters ) + throws + IOException, + FailingHttpStatusCodeException { + return getPage(webWindow, url, encType, method, parameters, getThrowExceptionOnFailingStatusCode()); + } + + /** + * Return a page. + * + * @param webWindow The window that the new page will be loaded into. + * @param url The url of the server + * @param method The submit method. Ie Submit.GET or SubmitMethod.POST + * @param parameters A list of {@link + * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that + * contain the parameters to send to the server + * @param throwExceptionOnFailingStatusCode true if this method should throw + * an exception whenever a failing status code is received. + * @return The page that was loaded. + * @exception IOException If an IO error occurs + * @exception FailingHttpStatusCodeException If the server returns a + * failing status code AND the variable + * "throwExceptionOnFailingStatusCode" is set to true + */ + public Page getPage( + final WebWindow webWindow, + final URL url, + final SubmitMethod method, + final List parameters, + final boolean throwExceptionOnFailingStatusCode ) + throws + IOException, + FailingHttpStatusCodeException { + return this.getPage(webWindow, url, FormEncodingType.URL_ENCODED, method, parameters, throwExceptionOnFailingStatusCode); + } + /** * Send a request to a server and return a Page that represents the * response from the server. This page will be used to populate this frame.<p> @@ -308,6 +364,7 @@ * * @param webWindow The window that the new page will be loaded into. * @param url The url of the server + * @param encType Encoding type of the form when done as a POST * @param method The submit method. Ie Submit.GET or SubmitMethod.POST * @param parameters A list of {@link * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that @@ -323,6 +380,7 @@ public Page getPage( final WebWindow webWindow, final URL url, + final FormEncodingType encType, final SubmitMethod method, final List parameters, final boolean throwExceptionOnFailingStatusCode ) @@ -336,7 +394,7 @@ webResponse = makeWebResponseForJavaScriptUrl(webWindow, url); } else { - webResponse = loadWebResponse( url, method, parameters ); + webResponse = loadWebResponse( url, encType, method, parameters ); } final String contentType = webResponse.getContentType(); final int statusCode = webResponse.getStatusCode(); @@ -947,17 +1005,32 @@ }; } + /** + * Load a {@link WebResponse} from the server + * @param url The url to load the response from. + * @param method The {@link SubmitMethod} to use + * @param parameters Any parameters that are being passed into the request + * @throws IOException if an IO problem occurs + * @return The WebResponse + */ + public final WebResponse loadWebResponse( + final URL url, final SubmitMethod method, final List parameters) + throws + IOException { + return this.loadWebResponse(url, FormEncodingType.URL_ENCODED, method, parameters); + } /** * Load a {@link WebResponse} from the server * @param url The url to load the response from. + * @param encType Encoding type of the form when done as a POST * @param method The {@link SubmitMethod} to use * @param parameters Any parameters that are being passed into the request * @throws IOException if an IO problem occurs * @return The WebResponse */ public final WebResponse loadWebResponse( - final URL url, final SubmitMethod method, final List parameters) + final URL url, final FormEncodingType encType, final SubmitMethod method, final List parameters) throws IOException { @@ -965,7 +1038,7 @@ Assert.notNull("method", method); Assert.notNull("parameters", parameters); - final WebResponse webResponse = getWebConnection().getResponse( url, method, parameters, requestHeaders_ ); + final WebResponse webResponse = getWebConnection().getResponse( url, encType, method, parameters, requestHeaders_ ); final int statusCode = webResponse.getStatusCode(); if( statusCode >= 301 && statusCode <=307 && isRedirectEnabled() ) { Index: src/java/com/gargoylesoftware/htmlunit/WebConnection.java =================================================================== RCS file: /cvsroot/htmlunit/htmlunit/src/java/com/gargoylesoftware/htmlunit/WebConnection.java,v retrieving revision 1.7 diff -u -r1.7 WebConnection.java --- src/java/com/gargoylesoftware/htmlunit/WebConnection.java 16 Jul 2003 20:11:21 -0000 1.7 +++ src/java/com/gargoylesoftware/htmlunit/WebConnection.java 12 Aug 2003 22:56:55 -0000 @@ -103,7 +103,26 @@ throws IOException; - + /** + * Submit a request and retrieve a response + * + * @param parameters Any parameters + * @param url The url of the server + * @param encType Encoding type of the form when done as a POST + * @param submitMethod The submit method. Ie SubmitMethod.GET + * @param requestHeaders Any headers that need to be put into the request. + * @return See above + * @exception IOException If an IO error occurs + */ + public abstract WebResponse getResponse( + final URL url, + final FormEncodingType encType, + final SubmitMethod submitMethod, + final List parameters, + final Map requestHeaders ) + throws + IOException; + /** * Return the web client * @return The web client. Index: src/java/com/gargoylesoftware/htmlunit/html/HtmlForm.java =================================================================== RCS file: /cvsroot/htmlunit/htmlunit/src/java/com/gargoylesoftware/htmlunit/html/HtmlForm.java,v retrieving revision 1.22 diff -u -r1.22 HtmlForm.java --- src/java/com/gargoylesoftware/htmlunit/html/HtmlForm.java 11 Aug 2003 15:24:57 -0000 1.22 +++ src/java/com/gargoylesoftware/htmlunit/html/HtmlForm.java 12 Aug 2003 22:56:55 -0000 @@ -37,6 +37,7 @@ */ package com.gargoylesoftware.htmlunit.html; +import com.gargoylesoftware.htmlunit.FormEncodingType; import com.gargoylesoftware.htmlunit.Assert; import com.gargoylesoftware.htmlunit.ElementNotFoundException; import com.gargoylesoftware.htmlunit.KeyValuePair; @@ -163,10 +164,10 @@ catch( final MalformedURLException e ) { throw new IllegalArgumentException( "Not a valid url: " + action ); } - + final FormEncodingType encType = FormEncodingType.getInstance( this.getEnctypeAttribute() ); final SubmitMethod method = SubmitMethod.getInstance( getAttributeValue( "method" ) ); final WebWindow webWindow = htmlPage.getEnclosingWindow(); - return htmlPage.getWebClient().getPage( webWindow, url, method, parameterList ); + return htmlPage.getWebClient().getPage( webWindow, url, encType, method, parameterList ); } Index: src/java/com/gargoylesoftware/htmlunit/html/HtmlInput.java =================================================================== RCS file: /cvsroot/htmlunit/htmlunit/src/java/com/gargoylesoftware/htmlunit/html/HtmlInput.java,v retrieving revision 1.25 diff -u -r1.25 HtmlInput.java --- src/java/com/gargoylesoftware/htmlunit/html/HtmlInput.java 23 Jul 2003 17:26:52 -0000 1.25 +++ src/java/com/gargoylesoftware/htmlunit/html/HtmlInput.java 12 Aug 2003 22:56:55 -0000 @@ -37,12 +37,15 @@ */ package com.gargoylesoftware.htmlunit.html; +import com.gargoylesoftware.htmlunit.KeyDataPair; import com.gargoylesoftware.htmlunit.Assert; import com.gargoylesoftware.htmlunit.ElementNotFoundException; import com.gargoylesoftware.htmlunit.KeyValuePair; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.ScriptResult; import org.w3c.dom.Element; + +import java.io.File; import java.io.IOException; /** @@ -107,7 +110,14 @@ }; } } - return new KeyValuePair[]{new KeyValuePair( getNameAttribute(), getValueAttribute() )}; + if (!this.getTypeAttribute().equals("file")) { + return new KeyValuePair[]{new KeyValuePair( getNameAttribute(), getValueAttribute() )}; + } else { + File f = new File(getValueAttribute()); + return new KeyValuePair[]{new KeyDataPair( getNameAttribute(), f )}; + } + + } Index: src/test/java/com/gargoylesoftware/htmlunit/FakeWebConnection.java =================================================================== RCS file: /cvsroot/htmlunit/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/FakeWebConnection.java,v retrieving revision 1.5 diff -u -r1.5 FakeWebConnection.java --- src/test/java/com/gargoylesoftware/htmlunit/FakeWebConnection.java 16 Jul 2003 20:11:20 -0000 1.5 +++ src/test/java/com/gargoylesoftware/htmlunit/FakeWebConnection.java 12 Aug 2003 22:56:55 -0000 @@ -110,17 +110,34 @@ super( webClient ); } + /** + * Submit a request to the processor + * + * @param url The url + * @param method The method to use + * @param parameters any parameters + * @return The response as an input stream + */ + public WebResponse getResponse( + final URL url, + final SubmitMethod method, + final List parameters, + final Map requestParameters ) { + return this.getResponse(url, FormEncodingType.URL_ENCODED, method, parameters, requestParameters); + } /** * Submit a request to the processor * * @param url The url + * @param encType form encoding type to use for POST method * @param method The method to use * @param parameters any parameters * @return The response as an input stream */ public WebResponse getResponse( final URL url, + final FormEncodingType encType, final SubmitMethod method, final List parameters, final Map requestParameters ) { |