KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > gargoylesoftware > htmlunit > WebClient


1 /*
2  * Copyright (c) 2002, 2005 Gargoyle Software Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice,
8  * this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12  * 3. The end-user documentation included with the redistribution, if any, must
13  * include the following acknowledgment:
14  *
15  * "This product includes software developed by Gargoyle Software Inc.
16  * (http://www.GargoyleSoftware.com/)."
17  *
18  * Alternately, this acknowledgment may appear in the software itself, if
19  * and wherever such third-party acknowledgments normally appear.
20  * 4. The name "Gargoyle Software" must not be used to endorse or promote
21  * products derived from this software without prior written permission.
22  * For written permission, please contact info@GargoyleSoftware.com.
23  * 5. Products derived from this software may not be called "HtmlUnit", nor may
24  * "HtmlUnit" appear in their name, without prior written permission of
25  * Gargoyle Software Inc.
26  *
27  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30  * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  */

38 package com.gargoylesoftware.htmlunit;
39
40 import java.io.BufferedInputStream JavaDoc;
41 import java.io.ByteArrayOutputStream JavaDoc;
42 import java.io.File JavaDoc;
43 import java.io.FileInputStream JavaDoc;
44 import java.io.IOException JavaDoc;
45 import java.io.InputStream JavaDoc;
46 import java.io.OutputStreamWriter JavaDoc;
47 import java.lang.reflect.Constructor JavaDoc;
48 import java.net.MalformedURLException JavaDoc;
49 import java.net.URL JavaDoc;
50 import java.net.URLConnection JavaDoc;
51 import java.net.URLStreamHandler JavaDoc;
52 import java.util.ArrayList JavaDoc;
53 import java.util.BitSet JavaDoc;
54 import java.util.Collections JavaDoc;
55 import java.util.HashMap JavaDoc;
56 import java.util.HashSet JavaDoc;
57 import java.util.Iterator JavaDoc;
58 import java.util.List JavaDoc;
59 import java.util.Map JavaDoc;
60 import java.util.Set JavaDoc;
61 import java.util.Stack JavaDoc;
62 import java.util.StringTokenizer JavaDoc;
63
64 import org.org.apache.commons.httpclient.URI;
65 import org.org.apache.commons.httpclient.URIException;
66 import org.org.apache.commons.httpclient.auth.CredentialsProvider;
67 import org.org.apache.commons.httpclient.util.URIUtil;
68 import org.apache.commons.io.FileUtils;
69 import org.apache.commons.io.IOUtils;
70 import org.apache.commons.logging.Log;
71 import org.apache.commons.logging.LogFactory;
72
73 import com.gargoylesoftware.htmlunit.html.BaseFrame;
74 import com.gargoylesoftware.htmlunit.html.FocusableElement;
75 import com.gargoylesoftware.htmlunit.html.HTMLParser;
76 import com.gargoylesoftware.htmlunit.html.HTMLParserListener;
77 import com.gargoylesoftware.htmlunit.html.HtmlPage;
78 import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
79
80 /**
81  * An object that represents a web browser
82  *
83  * @version $Revision: 100 $
84  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
85  * @author <a HREF="mailto:gudujarlson@sf.net">Mike J. Bresnahan</a>
86  * @author Dominique Broeglin
87  * @author Noboru Sinohara
88  * @author <a HREF="mailto:chen_jun@users.sourceforge.net"> Chen Jun</a>
89  * @author David K. Taylor
90  * @author <a HREF="mailto:cse@dynabean.de">Christian Sell</a>
91  * @author <a HREF="mailto:bcurren@esomnie.com">Ben Curren</a>
92  * @author Marc Guillemot
93  * @author Chris Erskine
94  * @author Daniel Gredler
95  * @author Sergey Gorelkin
96  * @author Hans Donner
97  */

98 public class WebClient {
99
100     private WebConnection webConnection_;
101     private boolean printContentOnFailingStatusCode_ = true;
102     private boolean throwExceptionOnFailingStatusCode_ = true;
103     private CredentialsProvider credentialsProvider_ = new DefaultCredentialsProvider();
104     private final String JavaDoc proxyHost_;
105     private final int proxyPort_;
106     private ScriptEngine scriptEngine_;
107     private boolean javaScriptEnabled_ = true;
108     private String JavaDoc homePage_;
109     private FocusableElement elementWithFocus_;
110     private final Map JavaDoc requestHeaders_ = Collections.synchronizedMap(new HashMap JavaDoc(89));
111
112     private AlertHandler alertHandler_;
113     private ConfirmHandler confirmHandler_;
114     private PromptHandler promptHandler_;
115     private StatusHandler statusHandler_;
116
117     private BrowserVersion browserVersion_ = BrowserVersion.getDefault();
118     private boolean isRedirectEnabled_ = true;
119     private PageCreator pageCreator_ = new DefaultPageCreator();
120
121     private final Set JavaDoc webWindowListeners_ = new HashSet JavaDoc(5);
122     private final List JavaDoc webWindows_ = Collections.synchronizedList(new ArrayList JavaDoc());
123
124     private WebWindow currentWindow_ = new TopLevelWindow("", this);
125     private Stack JavaDoc firstWindowStack_ = new Stack JavaDoc();
126     private int timeout_ = 0;
127     private HTMLParserListener htmlParserListener_;
128
129     private static URLStreamHandler JavaDoc JavaScriptUrlStreamHandler_
130         = new com.gargoylesoftware.htmlunit.protocol.javascript.Handler();
131     private static URLStreamHandler JavaDoc AboutUrlStreamHandler_
132         = new com.gargoylesoftware.htmlunit.protocol.about.Handler();
133
134     /**
135      * URL for "about:blank"
136      */

137     public static final URL JavaDoc URL_ABOUT_BLANK;
138     static {
139         URL JavaDoc tmpUrl = null;
140         try {
141             tmpUrl = new URL JavaDoc(null, "about:blank", AboutUrlStreamHandler_);
142         }
143         catch (final MalformedURLException JavaDoc e) {
144             // impossible
145
e.printStackTrace();
146         }
147         URL_ABOUT_BLANK = tmpUrl;
148     }
149
150     //singleton WebResponse for "about:blank"
151
private static final WebResponse WEB_RESPONSE_FOR_ABOUT_BLANK = new WebResponse() {
152         public int getStatusCode() {
153             return 200;
154         }
155         public String JavaDoc getStatusMessage() {
156             return "OK";
157         }
158         public String JavaDoc getContentType() {
159             return "text/html";
160         }
161         public String JavaDoc getContentAsString() {
162             return "";
163         }
164         public InputStream JavaDoc getContentAsStream() {
165             return TextUtil.toInputStream("");
166         }
167         public URL JavaDoc getUrl() {
168             return URL_ABOUT_BLANK;
169         }
170         public List JavaDoc getResponseHeaders() {
171             return Collections.EMPTY_LIST;
172         }
173         public String JavaDoc getResponseHeaderValue(final String JavaDoc key) {
174             return "";
175         }
176         public long getLoadTimeInMilliSeconds() {
177             return 0;
178         }
179         public byte[] getResponseBody() {
180             return "".getBytes();
181         }
182         public String JavaDoc getContentCharSet() {
183             return "ISO-8859-1";
184         }
185     };
186
187     private ScriptPreProcessor scriptPreProcessor_;
188     private Map JavaDoc activeXObjectMap_ = Collections.EMPTY_MAP;
189     private RefreshHandler refreshHandler_ = new ImmediateRefreshHandler();
190     private boolean throwExceptionOnScriptError_ = true;
191
192
193     /**
194      * Create an instance using {@link BrowserVersion#FULL_FEATURED_BROWSER}
195      */

196     public WebClient() {
197         this( BrowserVersion.FULL_FEATURED_BROWSER );
198     }
199
200
201     /**
202      * Create an instance using the specified {@link BrowserVersion}
203      * @param browserVersion The browser version to simulate
204      */

205     public WebClient( final BrowserVersion browserVersion ) {
206         Assert.notNull("browserVersion", browserVersion);
207
208         homePage_ = "http://www.gargoylesoftware.com/";
209         browserVersion_ = browserVersion;
210         proxyHost_ = null;
211         proxyPort_ = 0;
212         try {
213             scriptEngine_ = createJavaScriptEngineIfPossible( this );
214         }
215         catch( final NoClassDefFoundError JavaDoc e ) {
216             scriptEngine_ = null;
217         }
218     }
219
220
221     /**
222      * Create an instance that will use the specified {@link BrowserVersion} and proxy server
223      * @param browserVersion The browser version to simulate
224      * @param proxyHost The server that will act as proxy
225      * @param proxyPort The port to use on the proxy server
226      */

227     public WebClient( final BrowserVersion browserVersion, final String JavaDoc proxyHost, final int proxyPort ) {
228         Assert.notNull("browserVersion", browserVersion);
229         Assert.notNull( "proxyHost", proxyHost );
230
231         homePage_ = "http://www.gargoylesoftware.com/";
232         browserVersion_ = browserVersion;
233         proxyHost_ = proxyHost;
234         proxyPort_ = proxyPort;
235         try {
236             scriptEngine_ = createJavaScriptEngineIfPossible( this );
237         }
238         catch( final NoClassDefFoundError JavaDoc e ) {
239             scriptEngine_ = null;
240         }
241     }
242
243
244     /**
245      * Create a javascript engine if possible.
246      *
247      * @param webClient The webclient that we are creating the script engine for.
248      * @return A javascript engine or null if one could not be created.
249      */

250     public static JavaScriptEngine createJavaScriptEngineIfPossible( final WebClient webClient ) {
251         try {
252             Class.forName( "org.mozilla.javascript.Context" );
253             return new JavaScriptEngine( webClient );
254         }
255         catch( final ClassNotFoundException JavaDoc e ) {
256             return null;
257         }
258         catch( final NoClassDefFoundError JavaDoc e ) {
259             return null;
260         }
261     }
262
263
264     /**
265      * <p>Return the object that will resolve all url requests<p>
266      * <p>INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK</p>
267      * @return The connection that will be used.
268      */

269     public synchronized WebConnection getWebConnection() {
270         if( webConnection_ == null ) {
271             if( proxyHost_ == null ) {
272                 webConnection_ = new HttpWebConnection( this );
273             }
274             else {
275                 webConnection_ = new HttpWebConnection( this, proxyHost_, proxyPort_ );
276             }
277         }
278
279         return webConnection_;
280     }
281
282
283     /**
284      * Set the object that will resolve all url requests <p />
285      *
286      * This method is intended for unit testing HtmlUnit itself. It is not expected
287      * to change but you shouldn't need to call it during normal use of HtmlUnit.
288      *
289      * @param webConnection The new web connection
290      */

291     public void setWebConnection( final WebConnection webConnection ) {
292         Assert.notNull( "webConnection", webConnection );
293         webConnection_ = webConnection;
294     }
295
296     /**
297      * Send a request to a server and return a Page that represents the
298      * response from the server. This page will be used to populate this frame.<p>
299      *
300      * The type of Page will depend on the content type of the http response. <p />
301      *
302      * <table>
303      * <tr>
304      * <th>Content type</th>
305      * <th>Type of page</th>
306      * </tr>
307      * <tr>
308      * <td>"text/html"</td>
309      * <td>{@link com.gargoylesoftware.htmlunit.html.HtmlPage}</td>
310      * </tr>
311      * <tr>
312      * <td>"text/xhtml"</td>
313      * <td>{@link com.gargoylesoftware.htmlunit.html.HtmlPage}</td>
314      * </tr>
315      * <tr>
316      * <td>"application/xhtml+xml"</td>
317      * <td>{@link com.gargoylesoftware.htmlunit.html.HtmlPage} for now, in the
318      * future it will be XML validated as well
319      * </td>
320      * </tr>
321      * <tr>
322      * <td>"text/*"</td>
323      * <td>{@link com.gargoylesoftware.htmlunit.TextPage}</td>
324      * </tr>
325      * <tr>
326      * <td>Anything else</td>
327      * <td>{@link com.gargoylesoftware.htmlunit.UnexpectedPage}</td>
328      * </tr>
329      * </table>
330      *
331      *
332      * @param webWindow The WebWindow to load this request into
333      * @param parameters Parameter object for the web request
334      * @return See above
335      * @exception IOException If an IO error occurs
336      * @exception FailingHttpStatusCodeException If the server returns a
337      * failing status code AND the property
338      * {@link #setThrowExceptionOnFailingStatusCode(boolean)} is set to true
339      *
340      * @see WebRequestSettings
341      */

342     public Page getPage( final WebWindow webWindow, final WebRequestSettings parameters )
343         throws IOException JavaDoc, FailingHttpStatusCodeException {
344
345         getLog().debug("Get page for window named '" + webWindow.getName() + "', using " + parameters);
346
347         final String JavaDoc protocol = parameters.getURL().getProtocol();
348         final WebResponse webResponse;
349         if( protocol.equals("javascript") ) {
350             webResponse = makeWebResponseForJavaScriptUrl(webWindow, parameters.getURL());
351         }
352         else if (protocol.equals("about")) {
353             webResponse = makeWebResponseForAboutUrl(parameters.getURL());
354         }
355         else if (protocol.equals("file")) {
356             webResponse = makeWebResponseForFileUrl(parameters.getURL());
357         }
358         else {
359             webResponse = loadWebResponse(
360                     parameters.getURL(),
361                     parameters.getEncodingType(),
362                     parameters.getSubmitMethod(),
363                     parameters.getRequestParameters());
364         }
365         final String JavaDoc contentType = webResponse.getContentType();
366         final int statusCode = webResponse.getStatusCode();
367
368         final boolean wasResponseSuccessful = ( statusCode >= 200 && statusCode < 300 );
369
370         if( printContentOnFailingStatusCode_ && !wasResponseSuccessful) {
371             getLog().info( "statusCode=[" + statusCode
372                 + "] contentType=[" + contentType + "]" );
373             getLog().info( webResponse.getContentAsString() );
374         }
375
376         loadWebResponseInto(webResponse, webWindow);
377
378         if( getThrowExceptionOnFailingStatusCode() && !wasResponseSuccessful) {
379             throw new FailingHttpStatusCodeException( statusCode, webResponse.getStatusMessage() );
380         }
381
382         return webWindow.getEnclosedPage();
383     }
384
385     /**
386      * <p>For internal use only</p>
387      * <p>Open a new web window and populate it with a page loaded by
388      * {@link #getPage(WebWindow,WebRequestSettings)}</p>
389      *
390      * @param opener The web window that initiated the request.
391      * @param target The name of the window to be opened. This is the name that would
392      * be passed into the javascript open() method.
393      * @param params Any parameters
394      * @return The new page.
395      * @exception FailingHttpStatusCodeException If the server returns a
396      * failing status code AND the property
397      * {@link #setThrowExceptionOnFailingStatusCode(boolean)} is set to true.
398      * @exception IOException If an IO problem occurs.
399      */

400     public Page getPage(final WebWindow opener, final String JavaDoc target, final WebRequestSettings params)
401         throws FailingHttpStatusCodeException, IOException JavaDoc {
402         return getPage(openTargetWindow(opener, target, "_self"), params);
403     }
404
405     /**
406      * Convenience method to load a URL into the current WebWindow
407      * @param url The url of the new content.
408      * @return The new page.
409      * @throws FailingHttpStatusCodeException If the server returns a
410      * failing status code AND the property
411      * {@link #setThrowExceptionOnFailingStatusCode(boolean)} is set to true.
412      * @throws IOException If an IO problem occurs.
413      */

414     public Page getPage(final URL JavaDoc url) throws IOException JavaDoc, FailingHttpStatusCodeException {
415         return getPage(getCurrentWindow(), new WebRequestSettings(url));
416     }
417
418     /**
419      * Convenience method to load a web request into the current WebWindow
420      * @param request The request parameters
421      * @return The new page.
422      * @exception FailingHttpStatusCodeException If the server returns a
423      * failing status code AND the property
424      * {@link #setThrowExceptionOnFailingStatusCode(boolean)} is set to true.
425      * @exception IOException If an IO problem occurs.
426      * @see #getPage(WebWindow,WebRequestSettings)
427      */

428     public Page getPage(final WebRequestSettings request) throws IOException JavaDoc,
429             FailingHttpStatusCodeException {
430         return getPage(getCurrentWindow(), request);
431     }
432
433
434     /**
435      * Send a request to a server and return a Page that represents the
436      * response from the server. This is the same as calling {@link
437      * #getPage(WebWindow,URL,SubmitMethod,List)} with the current window.
438      *
439      * @param url The url of the server
440      * @param method The submit method to use when sending the request to the server
441      * @param parameters A list of {@link KeyValuePair}'s that are the parameters
442      * to the request
443      * @return See above
444      * @exception IOException If an IO error occurs
445      * @exception FailingHttpStatusCodeException If the server returns a
446      * failing status code AND the property
447      * "throwExceptionOnFailingStatusCode" is set to true (see {@link
448      * #setThrowExceptionOnFailingStatusCode(boolean)})
449      * @deprecated Use {@link #getPage(WebRequestSettings)}
450      */

451     public Page getPage(
452             final URL JavaDoc url,
453             final SubmitMethod method,
454             final List JavaDoc parameters )
455         throws
456             IOException JavaDoc,
457             FailingHttpStatusCodeException {
458
459         return getPage( getCurrentWindow(), url, method, parameters );
460     }
461
462
463     /**
464      * Return a page.
465      *
466      * @param webWindow The window that the new page will be loaded into.
467      * @param url The url of the server
468      * @param method The submit method. Ie Submit.GET or SubmitMethod.POST
469      * @param parameters A list of {@link
470      * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that
471      * contain the parameters to send to the server
472      * @return The page that was loaded.
473      * @exception IOException If an IO error occurs
474      * @exception FailingHttpStatusCodeException If the server returns a
475      * failing status code
476      * @see #setThrowExceptionOnFailingStatusCode(boolean)
477      * @see FailingHttpStatusCodeException
478      * @deprecated Use {@link #getPage(WebWindow, WebRequestSettings)}
479      */

480     public Page getPage(
481             final WebWindow webWindow,
482             final URL JavaDoc url,
483             final SubmitMethod method,
484             final List JavaDoc parameters )
485         throws
486             IOException JavaDoc,
487             FailingHttpStatusCodeException {
488         final WebRequestSettings settings = new WebRequestSettings(url, method);
489         settings.setRequestParameters(parameters);
490         return getPage(webWindow, settings);
491     }
492
493
494     /**
495      * Return a page.
496      *
497      * @param webWindow The window from which the load is initiated. If target
498      * is not specified then this will also be the window into which the new page
499      * is loaded.
500      * @param url The url of the server
501      * @param target The name of the window where the page will be loaded.
502      * @param method The submit method. Ie Submit.GET or SubmitMethod.POST
503      * @param parameters A list of {@link
504      * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that
505      * contain the parameters to send to the server
506      * @return The page that was loaded.
507      * @exception IOException If an IO error occurs
508      * @exception FailingHttpStatusCodeException If the server returns a
509      * failing status code
510      * @see #setThrowExceptionOnFailingStatusCode(boolean)
511      * @see FailingHttpStatusCodeException
512      * @deprecated Use {@link #getPage(WebWindow, WebRequestSettings)}
513      */

514     public Page getPage(
515             final WebWindow webWindow,
516             final URL JavaDoc url,
517             final String JavaDoc target,
518             final SubmitMethod method,
519             final List JavaDoc parameters )
520         throws
521             IOException JavaDoc,
522             FailingHttpStatusCodeException {
523         return getPage(openTargetWindow( webWindow, target, "_self" ), url,
524             method, parameters, getThrowExceptionOnFailingStatusCode());
525     }
526
527
528     /**
529      * Return a page.
530      *
531      * @param webWindow The window that the new page will be loaded into.
532      * @param url The url of the server
533      * @param encType Encoding type of the form when done as a POST
534      * @param method The submit method. Ie Submit.GET or SubmitMethod.POST
535      * @param parameters A list of {@link
536      * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that
537      * contain the parameters to send to the server
538      * @return The page that was loaded.
539      * @exception IOException If an IO error occurs
540      * @exception FailingHttpStatusCodeException If the server returns a
541      * failing status code AND the property
542      * "throwExceptionOnFailingStatusCode" is set to true (see {@link
543      * #setThrowExceptionOnFailingStatusCode(boolean)})
544      * @deprecated Use {@link #getPage(WebWindow, WebRequestSettings)}
545      */

546     public Page getPage(
547             final WebWindow webWindow,
548             final URL JavaDoc url,
549             final FormEncodingType encType,
550             final SubmitMethod method,
551             final List JavaDoc parameters )
552         throws
553             IOException JavaDoc,
554             FailingHttpStatusCodeException {
555         return getPage(webWindow, url, encType, method, parameters, getThrowExceptionOnFailingStatusCode());
556     }
557
558
559     /**
560      * Return a page.
561      *
562      * @param webWindow The window that the new page will be loaded into.
563      * @param url The url of the server
564      * @param target The name of the window where the page will be loaded.
565      * @param encType Encoding type of the form when done as a POST
566      * @param method The submit method. Ie Submit.GET or SubmitMethod.POST
567      * @param parameters A list of {@link
568      * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that
569      * contain the parameters to send to the server
570      * @return The page that was loaded.
571      * @exception IOException If an IO error occurs
572      * @exception FailingHttpStatusCodeException If the server returns a
573      * failing status code
574      * @see #setThrowExceptionOnFailingStatusCode(boolean)
575      * @see FailingHttpStatusCodeException
576      * @deprecated Use {@link #getPage(WebWindow, WebRequestSettings)}
577      */

578     public Page getPage(
579             final WebWindow webWindow,
580             final URL JavaDoc url,
581             final String JavaDoc target,
582             final FormEncodingType encType,
583             final SubmitMethod method,
584             final List JavaDoc parameters )
585         throws
586             IOException JavaDoc,
587             FailingHttpStatusCodeException {
588         return getPage(openTargetWindow( webWindow, target, "_self" ),
589             url, encType, method, parameters,
590             getThrowExceptionOnFailingStatusCode());
591     }
592
593
594     /**
595      * Return a page.
596      *
597      * @param webWindow The window that the new page will be loaded into.
598      * @param url The url of the server
599      * @param method The submit method. Ie Submit.GET or SubmitMethod.POST
600      * @param parameters A list of {@link
601      * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that
602      * contain the parameters to send to the server
603      * @param throwExceptionOnFailingStatusCode true if this method should throw
604      * an exception whenever a failing status code is received.
605      * @return The page that was loaded.
606      * @exception IOException If an IO error occurs
607      * @exception FailingHttpStatusCodeException If the server returns a
608      * failing status code AND the variable
609      * "throwExceptionOnFailingStatusCode" is set to true
610      * @see #setThrowExceptionOnFailingStatusCode(boolean)
611      * @see FailingHttpStatusCodeException
612      * @deprecated Use {@link #getPage(WebWindow, WebRequestSettings)}
613      */

614     public Page getPage(
615             final WebWindow webWindow,
616             final URL JavaDoc url,
617             final SubmitMethod method,
618             final List JavaDoc parameters,
619             final boolean throwExceptionOnFailingStatusCode )
620         throws
621             IOException JavaDoc,
622             FailingHttpStatusCodeException {
623         final WebRequestSettings settings = new WebRequestSettings(url);
624         settings.setSubmitMethod(method);
625         settings.setRequestParameters(parameters);
626         try {
627             return getPage(webWindow, settings);
628         }
629         catch (final FailingHttpStatusCodeException e) {
630             if (throwExceptionOnFailingStatusCode) {
631                 throw e;
632             }
633             else {
634                 return webWindow.getEnclosedPage();
635             }
636         }
637     }
638
639     /**
640      * @param webWindow The window that the new page will be loaded into.
641      * @param url The url of the server
642      * @param encType Encoding type of the form when done as a POST
643      * @param method The submit method. Ie Submit.GET or SubmitMethod.POST
644      * @param parameters A list of {@link
645      * com.gargoylesoftware.htmlunit.KeyValuePair KeyValuePair}'s that
646      * contain the parameters to send to the server
647      * @param throwExceptionOnFailingStatusCode true if this method should throw
648      * an exception whenever a failing status code is received.
649      * @return The page that was loaded.
650      * @exception IOException If an IO error occurs
651      * @exception FailingHttpStatusCodeException If the server returns a
652      * failing status code AND the variable
653      * "throwExceptionOnFailingStatusCode" is set to true
654      * @see #setThrowExceptionOnFailingStatusCode(boolean)
655      * @see FailingHttpStatusCodeException
656      * @deprecated Use {@link #getPage(WebWindow, WebRequestSettings)}
657      */

658     public Page getPage(
659             final WebWindow webWindow,
660             final URL JavaDoc url,
661             final FormEncodingType encType,
662             final SubmitMethod method,
663             final List JavaDoc parameters,
664             final boolean throwExceptionOnFailingStatusCode )
665         throws
666             IOException JavaDoc,
667             FailingHttpStatusCodeException {
668         final WebRequestSettings params = new WebRequestSettings(url, method);
669         params.setEncodingType(encType);
670         params.setRequestParameters(parameters);
671
672         try {
673             return getPage(webWindow, params);
674         }
675         catch (final FailingHttpStatusCodeException e){
676             if (throwExceptionOnFailingStatusCode){
677                 throw e;
678             }
679             else {
680                 return webWindow.getEnclosedPage();
681             }
682         }
683     }
684
685     /**
686      * Use the specified WebResponse to create a Page object which will then
687      * get inserted into the WebWindow. All initialization and event notification
688      * will be handled here.
689      *
690      * @param webResponse The response that will be used to create the new page.
691      * @param webWindow The window that the new page will be placed within.
692      * @throws IOException If an IO error occurs.
693      * @return The newly created page.
694      */

695     public Page loadWebResponseInto(
696             final WebResponse webResponse, final WebWindow webWindow )
697         throws
698             IOException JavaDoc {
699
700         Assert.notNull("webResponse", webResponse);
701         Assert.notNull("webWindow", webWindow);
702
703         final Page oldPage = webWindow.getEnclosedPage();
704         if (oldPage != null) {
705             // Remove the old windows before create new ones.
706
oldPage.cleanUp();
707         }
708
709         final Page newPage = pageCreator_.createPage( webResponse, webWindow );
710
711         if (! firstWindowStack_.empty() && firstWindowStack_.peek() == null) {
712             firstWindowStack_.pop();
713             firstWindowStack_.push(webWindow);
714         }
715
716         newPage.initialize();
717
718         fireWindowContentChanged( new WebWindowEvent(webWindow, WebWindowEvent.CHANGE, oldPage, newPage) );
719         return newPage;
720     }
721
722
723     /**
724      * Specify whether or not the content of the resulting document will be
725      * printed to the console in the event of a failing response code.
726      * Successful response codes are in the range 200-299. The default is true.
727      *
728      * @param enabled True to enable this feature
729      */

730     public void setPrintContentOnFailingStatusCode( final boolean enabled ) {
731         printContentOnFailingStatusCode_ = enabled;
732     }
733
734
735     /**
736      * Return true if the content of the resulting document will be printed to
737      * the console in the event of a failing response code.
738      *
739      * @return See above
740      * @see #setPrintContentOnFailingStatusCode
741      */

742     public boolean getPrintContentOnFailingStatusCode() {
743         return printContentOnFailingStatusCode_;
744     }
745
746
747     /**
748      * Specify whether or not an exception will be thrown in the event of a
749      * failing status code. Successful status codes are in the range 200-299.
750      * The default is true.
751      *
752      * @param enabled True to enable this feature
753      */

754     public void setThrowExceptionOnFailingStatusCode( final boolean enabled ) {
755         throwExceptionOnFailingStatusCode_ = enabled;
756     }
757
758
759     /**
760      * Return true if an exception will be thrown in the event of a failing
761      * response code.
762      *
763      * @return See above
764      * @see #setThrowExceptionOnFailingStatusCode
765      */

766     public boolean getThrowExceptionOnFailingStatusCode() {
767         return throwExceptionOnFailingStatusCode_;
768     }
769
770
771     /**
772      * Set a header which will be sent up on EVERY request from this client.
773      *
774      * @param name The name of the header
775      * @param value The value of the header
776      */

777     public void addRequestHeader( final String JavaDoc name, final String JavaDoc value ) {
778         requestHeaders_.put( name, value );
779     }
780
781
782     /**
783      * Remove a header
784      *
785      * @param name Name of the header
786      * @see #addRequestHeader
787      */

788     public void removeRequestHeader( final String JavaDoc name ) {
789         requestHeaders_.remove( name );
790     }
791
792
793     /**
794      * Sets the credentials provider that will provide authentication information when
795      * trying to access protected information on a web server. This information is
796      * required when the server is using Basic HTTP authentication, NTLM authentication,
797      * or Digest authentication.
798      * @param credentialsProvider The new credentials provider to use to authenticate.
799      */

800     public void setCredentialsProvider( final CredentialsProvider credentialsProvider ) {
801         Assert.notNull( "credentialsProvider", credentialsProvider );
802         credentialsProvider_ = credentialsProvider;
803     }
804
805
806     /**
807      * Returns the credentials provider for this client instance. By default, this
808      * method returns an instance of {@link DefaultCredentialsProvider}.
809      * @return The credentials provider for this client instance.
810      */

811     public CredentialsProvider getCredentialsProvider() {
812         return credentialsProvider_;
813     }
814
815
816     /**
817      * Throw an exception with the specified message. If junit is found in the
818      * classpath then a junit.framework.AssertionFailedError will be thrown
819      * (the same behaviour as calling fail() in junit). If junit is not found
820      * then an IllegalStateException will be thrown instead of the
821      * AssertionFailedError. <p>
822      *
823      * Override this to provide custom behaviour.
824      *
825      * @param message The failure message
826      */

827     public void assertionFailed( final String JavaDoc message ) {
828         try {
829             final Class JavaDoc clazz = junit.framework.AssertionFailedError.class;
830             final Constructor JavaDoc constructor = clazz.getConstructor( new Class JavaDoc[]{String JavaDoc.class} );
831             final Error JavaDoc error = ( Error JavaDoc )constructor.newInstance( new Object JavaDoc[]{message} );
832             throw error;
833         }
834         catch( final Exception JavaDoc e ) {
835             throw new IllegalStateException JavaDoc( message );
836         }
837     }
838
839
840     /**
841      * This method is intended for testing only - use at your own risk.
842      *
843      * @return the current script engine
844      */

845     public ScriptEngine getScriptEngine() {
846         if( javaScriptEnabled_ == true ) {
847             return scriptEngine_;
848         }
849         else {
850             return null;
851         }
852     }
853
854
855     /**
856      * This method is intended for testing only - use at your own risk.
857      *
858      * @param engine The new script engine to use.
859      */

860     public void setScriptEngine( final ScriptEngine engine ) {
861         scriptEngine_ = engine;
862     }
863
864
865     /**
866      * Enable/disable javascript support. By default, this property is enabled.
867      *
868      * @param isEnabled true to enable javascript support.
869      */

870     public void setJavaScriptEnabled( final boolean isEnabled ) {
871         javaScriptEnabled_ = isEnabled;
872     }
873
874
875     /**
876      * Return true if javascript is enabled and the script engine was loaded successfully.
877      *
878      * @return true if javascript is enabled.
879      */

880     public boolean isJavaScriptEnabled() {
881         return javaScriptEnabled_ && scriptEngine_ != null;
882     }
883
884
885     /**
886      * Returns the client's current homepage.
887      * @return the client's current homepage.
888      */

889     public String JavaDoc getHomePage() {
890         return homePage_;
891     }
892
893
894     /**
895      * Sets the client's homepage.
896      * @param homePage the new homepage URL
897      */

898     public void setHomePage(String JavaDoc homePage) {
899         homePage_ = homePage;
900     }
901
902
903     /**
904      * Set the alert handler for this webclient.
905      * @param alertHandler The new alerthandler or null if none is specified.
906      */

907     public void setAlertHandler( final AlertHandler alertHandler ) {
908         alertHandler_ = alertHandler;
909     }
910
911
912     /**
913      * Return the alert handler for this webclient.
914      * @return the alert handler or null if one hasn't been set.
915      */

916     public AlertHandler getAlertHandler() {
917         return alertHandler_;
918     }
919
920
921     /**
922      * Set the handler that will be executed when the javascript method Window.confirm() is called.
923      * @param handler The new handler or null if no handler is to be used.
924      */

925     public void setConfirmHandler( final ConfirmHandler handler ) {
926         confirmHandler_ = handler;
927     }
928
929
930     /**
931      * Return the confirm handler.
932      * @return the confirm handler or null if one hasn't been set.
933      */

934     public ConfirmHandler getConfirmHandler() {
935         return confirmHandler_;
936     }
937
938
939     /**
940      * Set the handler that will be executed when the javascript method Window.prompt() is called.
941      * @param handler The new handler or null if no handler is to be used.
942      */

943     public void setPromptHandler( final PromptHandler handler ) {
944         promptHandler_ = handler;
945     }
946
947
948     /**
949      * Return the prompt handler.
950      * @return the prompt handler or null if one hasn't been set.
951      */

952     public PromptHandler getPromptHandler() {
953         return promptHandler_;
954     }
955
956
957     /**
958      * Set the status handler for this webclient.
959      * @param statusHandler The new alerthandler or null if none is specified.
960      */

961     public void setStatusHandler( final StatusHandler statusHandler ) {
962         statusHandler_ = statusHandler;
963     }
964
965
966     /**
967      * Return the status handler for this webclient.
968      * @return the status handler or null if one hasn't been set.
969      */

970     public StatusHandler getStatusHandler() {
971         return statusHandler_;
972     }
973
974     /**
975      * Return the current browser version
976      * @return the current browser version.
977      */

978     public BrowserVersion getBrowserVersion() {
979         return browserVersion_;
980     }
981
982
983     /**
984      * Return the "current" window for this client. This is the window that will be used
985      * when getPage() is called without specifying a window.
986      * @return The current window.
987      */

988     public WebWindow getCurrentWindow() {
989         return currentWindow_;
990     }
991
992
993     /**
994      * Set the current window for this client. This is the window that will be used when
995      * getPage() is called without specifying a window.
996      * @param window The new window.
997      */

998     public void setCurrentWindow( final WebWindow window ) {
999         Assert.notNull("window", window);
1000        currentWindow_ = window;
1001    }
1002
1003
1004    /**
1005     * Return the "first" window for this client. This is the first window
1006     * opened since pushClearFirstWindow() was last called.
1007     * @return The first window.
1008     */

1009    public WebWindow popFirstWindow() {
1010        return (WebWindow)firstWindowStack_.pop();
1011    }
1012
1013
1014    /**
1015     * Clear the first window for this client.
1016     */

1017    public void pushClearFirstWindow() {
1018        firstWindowStack_.push(null);
1019    }
1020
1021
1022    /**
1023     * Add a listener for WebWindowEvent's. All events from all windows associated with this
1024     * client will be sent to the specified listener.
1025     * @param listener A listener.
1026     */

1027    public void addWebWindowListener( final WebWindowListener listener ) {
1028        Assert.notNull("listener", listener);
1029        webWindowListeners_.add(listener);
1030    }
1031
1032
1033    /**
1034     * Remove a listener for WebWindowEvent's.
1035     * @param listener A listener.
1036     */

1037    public void removeWebWindowListener( final WebWindowListener listener ) {
1038        Assert.notNull("listener", listener);
1039        webWindowListeners_.remove(listener);
1040    }
1041
1042
1043    private void fireWindowContentChanged( final WebWindowEvent event ) {
1044        final Iterator JavaDoc iterator = new ArrayList JavaDoc(webWindowListeners_).iterator();
1045        while( iterator.hasNext() ) {
1046            final WebWindowListener listener = (WebWindowListener)iterator.next();
1047            listener.webWindowContentChanged(event);
1048        }
1049    }
1050
1051
1052    private void fireWindowOpened( final WebWindowEvent event ) {
1053        final Iterator JavaDoc iterator = new ArrayList JavaDoc(webWindowListeners_).iterator();
1054        while( iterator.hasNext() ) {
1055            final WebWindowListener listener = (WebWindowListener)iterator.next();
1056            listener.webWindowOpened(event);
1057        }
1058    }
1059
1060
1061    private void fireWindowClosed( final WebWindowEvent event ) {
1062        final Iterator JavaDoc iterator = new ArrayList JavaDoc(webWindowListeners_).iterator();
1063        while( iterator.hasNext() ) {
1064            final WebWindowListener listener = (WebWindowListener)iterator.next();
1065            listener.webWindowClosed(event);
1066        }
1067    }
1068
1069    /**
1070     * Open a new window with the specified name. If the url is non-null then attempt to load
1071     * a page from that location and put it in the new window.
1072     *
1073     * @param url The url to load content from or null if no content is to be loaded.
1074     * @param windowName The name of the new window
1075     * @return The new window.
1076     */

1077    public WebWindow openWindow( final URL JavaDoc url, final String JavaDoc windowName ) {
1078        Assert.notNull("windowName", windowName);
1079        return openWindow( url, windowName, getCurrentWindow() );
1080    }
1081
1082
1083    /**
1084     * Open a new window with the specified name. If the url is non-null then attempt to load
1085     * a page from that location and put it in the new window.
1086     *
1087     * @param url The url to load content from or null if no content is to be loaded.
1088     * @param windowName The name of the new window
1089     * @param opener The web window that is calling openWindow
1090     * @return The new window.
1091     */

1092    public WebWindow openWindow( final URL JavaDoc url, final String JavaDoc windowName, final WebWindow opener ) {
1093        final WebWindow window = openTargetWindow( opener, windowName, "_blank" );
1094        if( url != null ) {
1095            try {
1096                getPage(window, url, SubmitMethod.GET, Collections.EMPTY_LIST);
1097            }
1098            catch( final IOException JavaDoc e ) {
1099                getLog().error("Error when loading content into window", e);
1100            }
1101        }
1102        return window;
1103    }
1104
1105
1106    /**
1107     * Open the window with the specified name. The name may be a special
1108     * target name of _self, _parent, _top, or _blank. An empty or null
1109     * name is set to the default. The special target names are relative to
1110     * the opener window.
1111     *
1112     * @param opener The web window that is calling openWindow
1113     * @param windowName The name of the new window
1114     * @param defaultName The default target if no name is given
1115     * @return The new window.
1116     */

1117    private WebWindow openTargetWindow(
1118            final WebWindow opener, final String JavaDoc windowName, final String JavaDoc defaultName) {
1119
1120        Assert.notNull("opener", opener);
1121        Assert.notNull("defaultName", defaultName);
1122
1123        String JavaDoc windowToOpen = windowName;
1124        if( windowToOpen == null || windowToOpen.length() == 0 ) {
1125            windowToOpen = defaultName;
1126        }
1127
1128        WebWindow webWindow = null;
1129        if( windowToOpen.equals("_self") ) {
1130            webWindow = opener;
1131            windowToOpen = "";
1132        }
1133        else if( windowToOpen.equals("_parent") ) {
1134            webWindow = opener.getParentWindow();
1135            windowToOpen = "";
1136        }
1137        else if( windowToOpen.equals("_top") ) {
1138            webWindow = opener.getTopWindow();
1139            windowToOpen = "";
1140        }
1141        else if( windowToOpen.equals("_blank") ) {
1142            // Leave window null to create a new window.
1143
windowToOpen = "";
1144        }
1145        else if( windowToOpen.length() != 0 ) {
1146            try {
1147                webWindow = getWebWindowByName(windowToOpen);
1148            }
1149            catch( final WebWindowNotFoundException e ) {
1150                // Fall through - a new window will be created below
1151
}
1152        }
1153
1154        if( webWindow == null ) {
1155            webWindow = new TopLevelWindow(windowToOpen, this);
1156            fireWindowOpened( new WebWindowEvent(webWindow, WebWindowEvent.OPEN, null, null) );
1157        }
1158
1159        if( webWindow instanceof TopLevelWindow && webWindow != opener.getTopWindow() ) {
1160            ((TopLevelWindow)webWindow).setOpener(opener);
1161        }
1162
1163        return webWindow;
1164    }
1165
1166
1167    /**
1168     * Set whether or not redirections will be followed automatically on receipt of
1169     * a redirect status code from the server.
1170     * @param enabled true to enable automatic redirection.
1171     */

1172    public void setRedirectEnabled( final boolean enabled ) {
1173        isRedirectEnabled_ = enabled;
1174    }
1175
1176
1177    /**
1178     * Return whether or not redirections will be followed automatically on receipt of
1179     * a redirect status code from the server.
1180     * @return true if automatic redirection is enabled.
1181     */

1182    public boolean isRedirectEnabled() {
1183        return isRedirectEnabled_;
1184    }
1185
1186
1187    /**
1188     * Set the object that will be used to create pages. Set this if you want
1189     * to customize the type of page that is returned for a given content type.
1190     *
1191     * @param pageCreator The new page creator
1192     */

1193    public void setPageCreator( final PageCreator pageCreator ) {
1194        Assert.notNull("pageCreator", pageCreator);
1195        pageCreator_ = pageCreator;
1196    }
1197
1198
1199    /**
1200     * Return the current page creator.
1201     *
1202     * @return the page creator
1203     */

1204    public PageCreator getPageCreator() {
1205        return pageCreator_;
1206    }
1207
1208
1209    /**
1210     * Return the first {@link WebWindow} that matches the specified name.
1211     *
1212     * @param name The name to search for.
1213     * @return The {@link WebWindow} with the specified name
1214     * @throws WebWindowNotFoundException If the {@link WebWindow} can't be found.
1215     */

1216    public WebWindow getWebWindowByName( final String JavaDoc name ) throws WebWindowNotFoundException {
1217        Assert.notNull("name", name);
1218
1219        final Iterator JavaDoc iterator = webWindows_.iterator();
1220        while( iterator.hasNext() ) {
1221            final WebWindow webWindow = (WebWindow)iterator.next();
1222            if( webWindow.getName().equals(name) ) {
1223                return webWindow;
1224            }
1225        }
1226
1227        throw new WebWindowNotFoundException(name);
1228    }
1229
1230
1231    /**
1232     * Add a new web window to the list of available windows. This is intended as
1233     * an internal api only and may change without notice..
1234     * @param webWindow The new WebWindow
1235     */

1236    public void registerWebWindow( final WebWindow webWindow ) {
1237        Assert.notNull("webWindow", webWindow);
1238        webWindows_.add(webWindow);
1239    }
1240
1241
1242    /**
1243     * Remove a web window to the list of available windows. This is intended as
1244     * an internal api only and may change without notice..
1245     * @param webWindow The WebWindow to remove
1246     */

1247    public void deregisterWebWindow( final WebWindow webWindow ) {
1248        Assert.notNull("webWindow", webWindow);
1249        webWindows_.remove(webWindow);
1250
1251        if( currentWindow_ == webWindow ) {
1252            if( webWindows_.size() == 0 ) {
1253                // Create a new one - we always have to have at least one window.
1254
currentWindow_ = new TopLevelWindow("", this);
1255            }
1256            else {
1257                currentWindow_ = (WebWindow)webWindows_.get(0);
1258            }
1259        }
1260        fireWindowClosed(new WebWindowEvent(webWindow, WebWindowEvent.CLOSE, webWindow.getEnclosedPage(), null));
1261    }
1262
1263
1264    /**
1265     * Return the log object for this web client
1266     * @return The log object
1267     */

1268    protected final Log getLog() {
1269        return LogFactory.getLog(getClass());
1270    }
1271
1272
1273    private static URL JavaDoc makeUrl( final String JavaDoc urlString ) throws MalformedURLException JavaDoc {
1274        Assert.notNull("urlString", urlString);
1275
1276        if( TextUtil.startsWithIgnoreCase(urlString, "javascript:") ) {
1277            return new URL JavaDoc(null, urlString, JavaScriptUrlStreamHandler_);
1278        }
1279        else if (TextUtil.startsWithIgnoreCase(urlString,"about:")){
1280            return new URL JavaDoc(null, urlString, AboutUrlStreamHandler_);
1281        }
1282        else {
1283            return new URL JavaDoc(urlString);
1284        }
1285    }
1286
1287
1288    /**
1289     * Expand a relative url relative to the specified base. In most situations
1290     * this is the same as <code>new URL(baseUrl, relativeUrl)</code> but
1291     * there are some cases that URL doesn't handle correctly.
1292     *
1293     * @param baseUrl The base url
1294     * @param relativeUrl The relative url
1295     * @return See above
1296     * @exception MalformedURLException If an error occurred when creating a URL
1297     * object
1298     * @see <a HREF="http://www.faqs.org/rfcs/rfc1808.html">RFC1808</a>
1299     * regarding Relative Uniform Resource Locators
1300     */

1301    public static URL JavaDoc expandUrl( final URL JavaDoc baseUrl, final String JavaDoc relativeUrl )
1302        throws MalformedURLException JavaDoc {
1303
1304        String JavaDoc parseUrl = relativeUrl;
1305        if (parseUrl == null) {
1306            parseUrl = "";
1307        }
1308
1309        // section 2.4.2 - parsing scheme
1310
final int schemeIndex = parseUrl.indexOf(":");
1311        if( schemeIndex != -1 ) {
1312            boolean isProtocolSpecified = true;
1313            for( int i=0; i<schemeIndex; i++ ) {
1314                if( Character.isLetter(parseUrl.charAt(i)) == false ) {
1315                    isProtocolSpecified = false;
1316                    break;
1317                }
1318            }
1319            if( isProtocolSpecified == true ) {
1320                return makeUrl( parseUrl);
1321            }
1322        }
1323
1324        // section 2.4.3 - parsing network location/login
1325
if( parseUrl.startsWith("//") ) {
1326            return makeUrl(baseUrl.getProtocol()+":"+parseUrl);
1327        }
1328
1329        // section 2.4.1 - parsing fragment
1330
final int fragmentIndex = parseUrl.lastIndexOf("#");
1331        if( fragmentIndex != -1 ) {
1332            parseUrl = parseUrl.substring(0, fragmentIndex);
1333        }
1334
1335
1336        // section 2.4.4 - parsing query
1337
String JavaDoc stringQuery = null;
1338        final int queryIndex = parseUrl.lastIndexOf("?");
1339        if( queryIndex != -1 ) {
1340            stringQuery = parseUrl.substring(queryIndex);
1341            parseUrl = parseUrl.substring(0, queryIndex);
1342        }
1343
1344        // section 2.4.5 - parsing parameters
1345
String JavaDoc stringParameters = null;
1346        final int parametersIndex = parseUrl.lastIndexOf(";");
1347        if( parametersIndex != -1 ) {
1348            stringParameters = parseUrl.substring(parametersIndex);
1349            parseUrl = parseUrl.substring(0, parametersIndex);
1350        }
1351
1352        // section 2.4.6 - parse path
1353
final List JavaDoc tokens = new ArrayList JavaDoc();
1354        final String JavaDoc stringToTokenize;
1355        if( parseUrl.trim().length() == 0 ) {
1356            stringToTokenize = baseUrl.getPath();
1357        }
1358        else if( parseUrl.startsWith("/") ) {
1359            stringToTokenize = parseUrl;
1360        }
1361        else {
1362            String JavaDoc path = baseUrl.getPath();
1363            if( !path.endsWith("/") && parseUrl.length() != 0) {
1364                path += "/..";
1365            }
1366            stringToTokenize = path+"/"+parseUrl;
1367        }
1368
1369        final String JavaDoc pathToTokenize = stringToTokenize;
1370        final StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(pathToTokenize, "/");
1371        while( tokenizer.hasMoreTokens() ) {
1372            tokens.add( tokenizer.nextToken() );
1373        }
1374
1375        for( int i=0; i<tokens.size(); i++ ) {
1376            final String JavaDoc oneToken = (String JavaDoc)tokens.get(i);
1377            if( oneToken.length() == 0 || oneToken.equals(".") ) {
1378                tokens.remove(i--);
1379            }
1380            else if( oneToken.equals("..") ) {
1381                tokens.remove(i--);
1382                if( i >= 0 ) {
1383                    tokens.remove(i--);
1384                }
1385            }
1386        }
1387
1388        final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
1389        buffer.append( baseUrl.getProtocol() );
1390        buffer.append( "://" );
1391        buffer.append( baseUrl.getHost() );
1392        final int port = baseUrl.getPort();
1393        if( port != -1 ) {
1394            buffer.append( ":" );
1395            buffer.append( port );
1396        }
1397
1398        final Iterator JavaDoc iterator = tokens.iterator();
1399        while( iterator.hasNext() ) {
1400            buffer.append("/");
1401            buffer.append(iterator.next());
1402        }
1403
1404        if( pathToTokenize.endsWith("/") ) {
1405            buffer.append("/");
1406        }
1407
1408        if (stringParameters != null) {
1409            buffer.append(stringParameters);
1410        }
1411        if (stringQuery != null) {
1412            buffer.append(stringQuery);
1413        }
1414        final String JavaDoc newUrlString = buffer.toString();
1415        return makeUrl( newUrlString );
1416    }
1417
1418    private WebResponse makeWebResponseForAboutUrl(final URL JavaDoc url) {
1419        if (!url.toExternalForm().substring("about:".length()).equalsIgnoreCase("blank")){
1420            throw new IllegalArgumentException JavaDoc(
1421                url.toExternalForm()+"is not supported, only about:blank is supported now.");
1422        }
1423        return WEB_RESPONSE_FOR_ABOUT_BLANK;
1424    }
1425
1426    /**
1427     * Builds a WebResponse for a file url.
1428     * This first implementation is basic.
1429     * It assumes that the file contains an html page encoded with computer's default
1430     * encoding.
1431     * @param url The file url
1432     * @return The web response
1433     * @throws IOException If an IO problem occurs
1434     */

1435    private WebResponse makeWebResponseForFileUrl(final URL JavaDoc url) throws IOException JavaDoc {
1436        final File JavaDoc file = FileUtils.toFile(url);
1437
1438        // take default encoding of the computer (in J2SE5 it's easier but...)
1439
final String JavaDoc encoding = (new OutputStreamWriter JavaDoc(new ByteArrayOutputStream JavaDoc())).getEncoding();
1440        final String JavaDoc str = FileUtils.readFileToString(file, encoding);
1441        final String JavaDoc contentType = guessContentType(file);
1442
1443        return new WebResponse() {
1444            public int getStatusCode() {
1445                return 200;
1446            }
1447            public String JavaDoc getStatusMessage() {
1448                return "OK";
1449            }
1450            public String JavaDoc getContentType() {
1451                return contentType;
1452            }
1453            public String JavaDoc getContentAsString() {
1454                return str;
1455            }
1456            public InputStream JavaDoc getContentAsStream() {
1457                return TextUtil.toInputStream(str);
1458            }
1459            public URL JavaDoc getUrl() {
1460                return url;
1461            }
1462            public List JavaDoc getResponseHeaders() {
1463                return Collections.EMPTY_LIST;
1464            }
1465            public String JavaDoc getResponseHeaderValue(final String JavaDoc key) {
1466                return "";
1467            }
1468            public long getLoadTimeInMilliSeconds() {
1469                return 0;
1470            }
1471            public byte[] getResponseBody() {
1472                return str.getBytes();
1473            }
1474            public String JavaDoc getContentCharSet() {
1475                return encoding;
1476            }
1477        };
1478    }
1479
1480    /**
1481     * Tries to guess the content type of the file.<br/>
1482     * This utility could be located in an helper class but we can compare this functionnality
1483     * for instance with the "Helper Applications" settings of Mozilla and therefore see it as a
1484     * property of the "browser".
1485     * @param file the file
1486     * @return "application/octet-stream" if nothing could be guessed.
1487     */

1488    public String JavaDoc guessContentType(final File JavaDoc file) {
1489        String JavaDoc contentType = null;
1490        InputStream JavaDoc inputStream = null;
1491        try {
1492            inputStream = new BufferedInputStream JavaDoc(new FileInputStream JavaDoc(file));
1493            contentType = URLConnection.guessContentTypeFromStream(inputStream);
1494        }
1495        catch (final IOException JavaDoc e) {
1496            // nothing, silently ignore
1497
}
1498        finally {
1499            IOUtils.closeQuietly(inputStream);
1500        }
1501
1502        if (contentType == null) {
1503            contentType = URLConnection.guessContentTypeFromName(file.getName());
1504        }
1505        if (contentType == null) {
1506            contentType = "application/octet-stream";
1507        }
1508
1509        return contentType;
1510    }
1511
1512    private WebResponse makeWebResponseForJavaScriptUrl( final WebWindow webWindow, final URL JavaDoc url ) {
1513        if (!(webWindow instanceof BaseFrame.FrameWindow)) {
1514            throw new IllegalArgumentException JavaDoc(
1515                "javascript urls can only be used to load content into frames and iframes");
1516        }
1517
1518        final BaseFrame.FrameWindow frameWindow = (BaseFrame.FrameWindow) webWindow;
1519        final HtmlPage enclosingPage = frameWindow.getEnclosingPage();
1520        final ScriptResult scriptResult = enclosingPage.executeJavaScriptIfPossible(
1521            url.toExternalForm(), "javascript url", false, null );
1522
1523        final String JavaDoc contentString = scriptResult.getJavaScriptResult().toString();
1524        return new StringWebResponse(contentString);
1525    }
1526
1527    /**
1528     * Load a {@link WebResponse} from the server
1529     * @param url The url to load the response from.
1530     * @param method The {@link SubmitMethod} to use
1531     * @param parameters Any parameters that are being passed into the request
1532     * @throws IOException if an IO problem occurs
1533     * @return The WebResponse
1534     * @deprecated Use {@link #loadWebResponse(WebRequestSettings)}
1535     */

1536    public final WebResponse loadWebResponse(
1537            final URL JavaDoc url, final SubmitMethod method, final List JavaDoc parameters)
1538        throws
1539            IOException JavaDoc {
1540        final WebRequestSettings wrs= new WebRequestSettings(url, method);
1541        wrs.setRequestParameters(parameters);
1542        return loadWebResponse(wrs);
1543    }
1544
1545    /**
1546     * Load a {@link WebResponse} from the server
1547     * @param url The url to load the response from.
1548     * @param encType Encoding type of the form when done as a POST
1549     * @param method The {@link SubmitMethod} to use
1550     * @param parameters Any parameters that are being passed into the request
1551     * @throws IOException if an IO problem occurs
1552     * @return The WebResponse
1553     * @deprecated Use {@link #loadWebResponse(WebRequestSettings)}
1554     */

1555    public final WebResponse loadWebResponse(
1556            final URL JavaDoc url, final FormEncodingType encType, final SubmitMethod method, final List JavaDoc parameters)
1557        throws
1558            IOException JavaDoc {
1559        final WebRequestSettings wrs = new WebRequestSettings(url, method);
1560        wrs.setEncodingType(encType);
1561        wrs.setRequestParameters(parameters);
1562        return loadWebResponse(wrs);
1563    }
1564    /**
1565     * Load a {@link WebResponse} from the server
1566     * @param webRequestSettings settings to use when making the request
1567     * @throws IOException if an IO problem occurs
1568     * @return The WebResponse
1569     */

1570    public final WebResponse loadWebResponse(final WebRequestSettings webRequestSettings)
1571        throws
1572            IOException JavaDoc {
1573        final URL JavaDoc url = webRequestSettings.getURL();
1574        final SubmitMethod method = webRequestSettings.getSubmitMethod();
1575        final List JavaDoc parameters = webRequestSettings.getRequestParameters();
1576
1577        Assert.notNull("url", url);
1578        Assert.notNull("method", method);
1579        Assert.notNull("parameters", parameters);
1580
1581        getLog().debug("Load response for " + url.toExternalForm());
1582
1583        //TODO: this should probably be handled inside of WebRequestSettings and
1584
// could cause a bug if anything above here reads the url again
1585
final URL JavaDoc fixedUrl = encodeUrl(url);
1586        webRequestSettings.setURL(fixedUrl);
1587
1588        final WebResponse webResponse = getWebConnection().getResponse(webRequestSettings);
1589        final int statusCode = webResponse.getStatusCode();
1590
1591        if( statusCode >= 301 && statusCode <=307 && isRedirectEnabled() ) {
1592            URL JavaDoc newUrl = null;
1593            String JavaDoc locationString = null;
1594            try {
1595                locationString = webResponse.getResponseHeaderValue("Location");
1596                if( locationString != null ) {
1597                    // HttpClient sometimes returns a delimited list of values where the delimiter is a comma.
1598
// We'll take a guess and go with the first value.
1599
final int indexOfComma = locationString.indexOf(',');
1600                    if( indexOfComma >= 0) {
1601                        newUrl = expandUrl( fixedUrl, locationString.substring(0, indexOfComma));
1602                    }
1603                    else {
1604                        newUrl = expandUrl( fixedUrl, locationString);
1605                    }
1606                }
1607            }
1608            catch( final MalformedURLException JavaDoc e ) {
1609                getLog().warn("Got a redirect status code ["+statusCode+" "
1610                    +webResponse.getStatusMessage()
1611                    +"] but the location is not a valid url ["+locationString+"]");
1612            }
1613            getLog().debug("Got a redirect status code ["+statusCode
1614                +"] new location=["+locationString+"]");
1615            if( webResponse.getUrl().toExternalForm().equals(locationString) ) {
1616                getLog().warn("Got a redirect but the location is the same as the page we just loaded ["
1617                    +locationString+"]");
1618            }
1619            else if( newUrl == null ) {
1620                // We don't have a new location to go to so just fall through
1621
}
1622            else if( ( statusCode == 301 || statusCode == 307 )
1623                && method.equals(SubmitMethod.GET) ) {
1624
1625                final WebRequestSettings wrs = new WebRequestSettings(newUrl);
1626                wrs.setRequestParameters(parameters);
1627                return loadWebResponse(wrs);
1628            }
1629            else if( statusCode == 302 || statusCode == 303 ) {
1630                final WebRequestSettings wrs = new WebRequestSettings(newUrl);
1631                return loadWebResponse(wrs);
1632            }
1633        }
1634
1635        return webResponse;
1636    }
1637
1638    /**
1639     * Encodes illegal parameter in query string (if any) as done by browsers.
1640     * Example: change "http://first?a=b c" to "http://first?a=b%20c"
1641     * @param url the url to encode
1642     * @return the provided url if no change needed, the fixed url else
1643     * @throws MalformedURLException if the new URL could note be instantiated
1644     * @throws URIException if the default protocol charset is not supported
1645     */

1646    protected URL JavaDoc encodeUrl(final URL JavaDoc url) throws MalformedURLException JavaDoc, URIException {
1647        // just look at urls with query string (better test?)
1648
final String JavaDoc str = url.toExternalForm();
1649        final int queryStart = url.toExternalForm().indexOf('?');
1650        if (queryStart != -1) {
1651            // extract query string: browsers seem not to encode everything, for instance not "#"
1652
final String JavaDoc query;
1653            final int anchorStart = str.indexOf('#');
1654            if (anchorStart < queryStart) {
1655                query = str.substring(queryStart);
1656            }
1657            else {
1658                query = str.substring(queryStart, anchorStart);
1659            }
1660
1661            // url may be partially encoded like "http://first?a=b%20c&d=e f"
1662
// // don't re-encode the %'s from already encoded items
1663
final BitSet JavaDoc partiallyEncodedQuery = new BitSet JavaDoc(256);
1664            partiallyEncodedQuery.set('%');
1665            partiallyEncodedQuery.or(URI.allowed_query);
1666            final String JavaDoc fixedQuery = URIUtil.encode(query, partiallyEncodedQuery);
1667            if (query.equals(fixedQuery)) {
1668                return url;
1669            }
1670            else {
1671                final StringBuffer JavaDoc newUrl = new StringBuffer JavaDoc(str);
1672                newUrl.replace(queryStart, queryStart + query.length(), fixedQuery);
1673                return new URL JavaDoc(newUrl.toString());
1674            }
1675        }
1676        else {
1677            return url;
1678        }
1679
1680    }
1681
1682    /**
1683     * Remove the focus to the specified component. This will trigger any relevant javascript
1684     * event handlers.
1685     *
1686     * @param oldElement The element that will lose the focus.
1687     * @see #moveFocusToElement(FocusableElement)
1688     * @return true if the focus changed and a new page was not loaded
1689     */

1690    public boolean moveFocusFromElement( final FocusableElement oldElement ) {
1691        if (oldElement != null && elementWithFocus_ == oldElement) {
1692            final String JavaDoc onBlurHandler = oldElement.getAttributeValue("onblur");
1693            if( onBlurHandler.length() != 0 ) {
1694                final HtmlPage currentPage = oldElement.getPage();
1695                final Page newPage = currentPage.executeJavaScriptIfPossible(
1696                    onBlurHandler, "OnBlur handler", true, oldElement).getNewPage();
1697
1698                // If a page reload happened as a result of the focus change then obviously this
1699
// element will not have the focus because its page has gone away.
1700
if( currentPage != newPage ) {
1701                    elementWithFocus_ = null;
1702                    return false;
1703                }
1704            }
1705            elementWithFocus_ = null;
1706            return true;
1707        }
1708        return false;
1709    }
1710
1711
1712    /**
1713     * Move the focus to the specified component. This will trigger any relevant javascript
1714     * event handlers.
1715     *
1716     * @param newElement The element that will recieve the focus.
1717     * @return true if the specified element now has the focus.
1718     * @see #getElementWithFocus()
1719     * @see HtmlPage#tabToNextElement()
1720     * @see HtmlPage#tabToPreviousElement()
1721     * @see HtmlPage#pressAccessKey(char)
1722     * @see HtmlPage#assertAllTabIndexAttributesSet()
1723     */

1724    public boolean moveFocusToElement( final FocusableElement newElement ) {
1725
1726        if( newElement == null ) {
1727            throw new IllegalArgumentException JavaDoc("Cannot move focus to null");
1728        }
1729
1730        if( elementWithFocus_ == newElement ) {
1731            // nothing to do
1732
return true;
1733        }
1734
1735        // blur for previous element with focus... only if it belongs to the same page
1736
if( elementWithFocus_ != null && elementWithFocus_.getPage() == newElement.getPage()) {
1737            elementWithFocus_.blur();
1738        }
1739
1740        final String JavaDoc onFocusHandler = newElement.getAttributeValue("onfocus");
1741        if( onFocusHandler.length() != 0 ) {
1742            final HtmlPage currentPage = newElement.getPage();
1743            final Page newPage = currentPage.executeJavaScriptIfPossible(
1744                onFocusHandler, "OnFocus handler", true, newElement).getNewPage();
1745
1746            // If a page reload happened as a result of the focus change then obviously this
1747
// element will not have the focus because its page has gone away.
1748
if( currentPage != newPage ) {
1749                elementWithFocus_ = null;
1750                return false;
1751            }
1752        }
1753
1754        elementWithFocus_ = newElement;
1755        return true;
1756    }
1757
1758
1759    /**
1760     * Return the element with the focus or null if no elements have the focus.
1761     *
1762     * @return The element with focus or null.
1763     * @see #moveFocusToElement(FocusableElement)
1764     */

1765    public FocusableElement getElementWithFocus() {
1766        return elementWithFocus_;
1767    }
1768
1769    /**
1770     * Return an immutable list of open web windows (top windows or not).
1771     * @return The web windows
1772     */

1773    public List JavaDoc getWebWindows() {
1774        return Collections.unmodifiableList(webWindows_);
1775    }
1776
1777    /**
1778     * Set the handler to be used whenever a refresh is triggered. Refer
1779     * to the documentation for {@link RefreshHandler} for more details.
1780     * @param handler The new handler
1781     */

1782    public void setRefreshHandler( final RefreshHandler handler ) {
1783        if( handler == null ) {
1784            refreshHandler_ = new ImmediateRefreshHandler();
1785        }
1786        else {
1787            refreshHandler_ = handler;
1788        }
1789    }
1790
1791    /**
1792     * Return the current refresh handler or null if one has not been set.
1793     * @return The current RefreshHandler or null
1794     */

1795    public RefreshHandler getRefreshHandler() {
1796        return refreshHandler_;
1797    }
1798
1799    /**
1800     * Set the script pre processor for this webclient.
1801     * @param scriptPreProcessor The new preprocessor or null if none is specified
1802     */

1803    public void setScriptPreProcessor( final ScriptPreProcessor scriptPreProcessor ) {
1804        scriptPreProcessor_ = scriptPreProcessor;
1805    }
1806
1807
1808    /**
1809     * Return the script pre processor for this webclient.
1810     * @return the pre processor or null of one hasn't been set.
1811     */

1812    public ScriptPreProcessor getScriptPreProcessor() {
1813        return scriptPreProcessor_;
1814    }
1815
1816    /**
1817     * Set the active X object map for this webclient. The <code>Map</code> is used to map the
1818     * string passed into the <code>ActiveXObject</code> constructor to a java class name. Therefore
1819     * you can emulate <code>ActiveXObject</code>s in a web page's javascript by mapping the object
1820     * name to a java class to emulate the active X object.
1821     * @param activeXObjectMap The new preprocessor or null if none is specified
1822     */

1823    public void setActiveXObjectMap( final Map JavaDoc activeXObjectMap ) {
1824        activeXObjectMap_ = activeXObjectMap;
1825    }
1826
1827
1828    /**
1829     * Return the active X object map for this webclient.
1830     * @return the active X object map.
1831     */

1832    public Map JavaDoc getActiveXObjectMap() {
1833        return activeXObjectMap_;
1834    }
1835
1836    /**
1837     * Defines a listener for messages generated by the html parser.<br/>
1838     * <b>Note</b>: If {@link #getIgnoreOutsideContent()} returns <code>false</code>, the parser
1839     * will ignore closing &lt;body&gt; and &lt;html&gt; tags to be able to handle html content
1840     * incorectly located after the end of the html file. As a consequence it will finally
1841     * notify as errors that &lt;body&gt; and &lt;html&gt; are not closed properly even if
1842     * they were correctly present.
1843     * @param listener the new listener, <code>null</code> if messages should be totally ignored.
1844     */

1845    public void setHTMLParserListener(final HTMLParserListener listener) {
1846        htmlParserListener_ = listener;
1847    }
1848
1849    /**
1850     * Gets the configured listener for messages generated by the html parser.
1851     * @return <code>null</code> if no listener is defined (default value).
1852     */

1853    public HTMLParserListener getHTMLParserListener() {
1854        return htmlParserListener_;
1855    }
1856
1857    /**
1858     * Set the flag on the HtmlParse to ignore the content that is outside of the BODY
1859     * and HTML tags.
1860     * @param ignoreOutsideContent The boolean flag to enable or disable the support of
1861     * content outside of the HTML and BODY tags
1862     */

1863    public static void setIgnoreOutsideContent(final boolean ignoreOutsideContent) {
1864        HTMLParser.setIgnoreOutsideContent(ignoreOutsideContent);
1865    }
1866
1867    /**
1868     * Get the state of the flag to ignore contant outside the BODY and HTML tags
1869     * @return - The current state
1870     */

1871    public static boolean getIgnoreOutsideContent() {
1872        return HTMLParser.getIgnoreOutsideContent();
1873    }
1874
1875    /**
1876     * Gets the timeout value for the WebConnection
1877     *
1878     * @return The timeout value in milliseconds
1879     * @see WebClient#setTimeout(int)
1880     */

1881    public int getTimeout() {
1882        return timeout_;
1883    }
1884    /**
1885     * Sets the timeout of the WebConnection. Set to zero (the default) for an infinite wait.
1886     *
1887     * Note: The timeout is used twice. The first is for making the socket connection, the
1888     * second is for data retrieval. If the time is critical you must allow for twice the
1889     * time specified here.
1890     *
1891     * @param timeout The value of the timeout in milliseconds
1892     */

1893    public void setTimeout(final int timeout){
1894        timeout_ = timeout;
1895    }
1896
1897    /**
1898     * Indicates if an exception should be thrown when a script execution fails
1899     * (the default) or if it should be catched and just logged to allow page
1900     * execution to continue.
1901     * @return <code>true</code> if an exception is thrown on script error (the default)
1902     */

1903    public boolean isThrowExceptionOnScriptError() {
1904        return throwExceptionOnScriptError_;
1905    }
1906
1907
1908    /**
1909     * Changes the behavior of this webclient when a script error occurs.
1910     * @param newValue indicates if exception should be thrown or not
1911     */

1912    public void setThrowExceptionOnScriptError(final boolean newValue) {
1913        throwExceptionOnScriptError_ = newValue;
1914    }
1915}
1916
1917
Popular Tags