0001: package com.meterware.httpunit;
0002:
0003: /********************************************************************************************************************
0004: * $Id: WebResponse.java,v 1.140 2004/12/02 03:43:20 russgold Exp $
0005: *
0006: * Copyright (c) 2000-2004, Russell Gold
0007: *
0008: * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
0009: * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
0010: * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
0011: * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
0012: *
0013: * The above copyright notice and this permission notice shall be included in all copies or substantial portions
0014: * of the Software.
0015: *
0016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
0017: * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0018: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
0019: * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
0020: * DEALINGS IN THE SOFTWARE.
0021: *
0022: *******************************************************************************************************************/
0023: import com.meterware.httpunit.scripting.ScriptableDelegate;
0024: import com.meterware.httpunit.scripting.NamedDelegate;
0025: import com.meterware.httpunit.cookies.CookieJar;
0026: import com.meterware.httpunit.cookies.CookieSource;
0027:
0028: import java.io.*;
0029: import java.net.HttpURLConnection;
0030: import java.net.URL;
0031: import java.net.URLConnection;
0032: import java.net.MalformedURLException;
0033: import java.util.Hashtable;
0034: import java.util.Vector;
0035: import java.util.zip.GZIPInputStream;
0036:
0037: import org.w3c.dom.Document;
0038: import org.xml.sax.InputSource;
0039: import org.xml.sax.SAXException;
0040:
0041: /**
0042: * A response to a web request from a web server.
0043: *
0044: * @author <a href="mailto:russgold@httpunit.org">Russell Gold</a>
0045: * @author <a href="mailto:DREW.VARNER@oracle.com">Drew Varner</a>
0046: * @author <a href="mailto:dglo@ssec.wisc.edu">Dave Glowacki</a>
0047: * @author <a href="mailto:bx@bigfoot.com">Benoit Xhenseval</a>
0048: **/
0049: abstract public class WebResponse implements HTMLSegment, CookieSource {
0050:
0051: private static final String HTML_CONTENT = "text/html";
0052: private static final String XHTML_CONTENT = "application/xhtml+xml";
0053: private static final String FAUX_XHTML_CONTENT = "text/xhtml";
0054:
0055: private static final int UNINITIALIZED_INT = -2;
0056: private static final int UNKNOWN_LENGTH_TIMEOUT = 500;
0057: private static final int UNKNOWN_LENGTH_RETRY_INTERVAL = 10;
0058:
0059: private FrameSelector _frame;
0060:
0061: private String _baseTarget;
0062: private String _refreshHeader;
0063: private boolean _hasSubframes;
0064: private URL _baseURL;
0065: private boolean _parsingPage;
0066:
0067: /**
0068: * Returns a web response built from a URL connection. Provided to allow
0069: * access to WebResponse parsing without using a WebClient.
0070: **/
0071: public static WebResponse newResponse(URLConnection connection)
0072: throws IOException {
0073: return new HttpWebResponse(null, FrameSelector.TOP_FRAME,
0074: connection.getURL(), connection, HttpUnitOptions
0075: .getExceptionsThrownOnErrorStatus());
0076: }
0077:
0078: /**
0079: * Returns true if the response is HTML.
0080: **/
0081: public boolean isHTML() {
0082: return getContentType().equalsIgnoreCase(HTML_CONTENT)
0083: || getContentType()
0084: .equalsIgnoreCase(FAUX_XHTML_CONTENT)
0085: || getContentType().equalsIgnoreCase(XHTML_CONTENT);
0086: }
0087:
0088: /**
0089: * Returns the URL which invoked this response.
0090: **/
0091: public URL getURL() {
0092: return _pageURL;
0093: }
0094:
0095: /**
0096: * Returns the title of the page.
0097: * @exception SAXException thrown if there is an error parsing this response
0098: **/
0099: public String getTitle() throws SAXException {
0100: return getReceivedPage().getTitle();
0101: }
0102:
0103: /**
0104: * Returns the stylesheet linked in the head of the page.
0105: * <code>
0106: * <link type="text/css" rel="stylesheet" href="/mystyle.css" />
0107: * </code>
0108: * will return "/mystyle.css".
0109: * @exception SAXException thrown if there is an error parsing this response
0110: **/
0111: public String getExternalStyleSheet() throws SAXException {
0112: return getReceivedPage().getExternalStyleSheet();
0113: }
0114:
0115: /**
0116: * Retrieves the "content" of the meta tags for a key pair attribute-attributeValue.
0117: * <code>
0118: * <meta name="robots" content="index" />
0119: * <meta name="robots" content="follow" />
0120: * <meta http-equiv="Expires" content="now" />
0121: * </code>
0122: * this can be used like this
0123: * <code>
0124: * getMetaTagContent("name","robots") will return { "index","follow" }
0125: * getMetaTagContent("http-equiv","Expires") will return { "now" }
0126: * </code>
0127: * @exception SAXException thrown if there is an error parsing this response
0128: **/
0129: public String[] getMetaTagContent(String attribute,
0130: String attributeValue) throws SAXException {
0131: return getReceivedPage().getMetaTagContent(attribute,
0132: attributeValue);
0133: }
0134:
0135: /**
0136: * Returns the name of the frame containing this page.
0137: **/
0138: public String getFrameName() {
0139: return _frame.getName();
0140: }
0141:
0142: void setFrame(FrameSelector frame) {
0143: if (!_frame.getName().equals(frame.getName()))
0144: throw new IllegalArgumentException(
0145: "May not modify the frame name");
0146: _frame = frame;
0147: }
0148:
0149: /**
0150: * Returns the frame containing this page.
0151: */
0152: FrameSelector getFrame() {
0153: return _frame;
0154: }
0155:
0156: /**
0157: * Returns a request to refresh this page, if any. This request will be defined
0158: * by a <meta> tag in the header. If no tag exists, will return null.
0159: **/
0160: public WebRequest getRefreshRequest() {
0161: readRefreshRequest();
0162: return _refreshRequest;
0163: }
0164:
0165: /**
0166: * Returns the delay before normally following the request to refresh this page, if any.
0167: * This request will be defined by a <meta> tag in the header. If no tag exists,
0168: * will return zero.
0169: **/
0170: public int getRefreshDelay() {
0171: readRefreshRequest();
0172: return _refreshDelay;
0173: }
0174:
0175: /**
0176: * Returns the response code associated with this response.
0177: **/
0178: abstract public int getResponseCode();
0179:
0180: /**
0181: * Returns the response message associated with this response.
0182: **/
0183: abstract public String getResponseMessage();
0184:
0185: /**
0186: * Returns the content length of this response.
0187: * @return the content length, if known, or -1.
0188: */
0189: public int getContentLength() {
0190: if (_contentLength == UNINITIALIZED_INT) {
0191: String length = getHeaderField("Content-Length");
0192: _contentLength = (length == null) ? -1 : Integer
0193: .parseInt(length);
0194: }
0195: return _contentLength;
0196: }
0197:
0198: /**
0199: * Returns the content type of this response.
0200: **/
0201: public String getContentType() {
0202: if (_contentType == null)
0203: readContentTypeHeader();
0204: return _contentType;
0205: }
0206:
0207: /**
0208: * Returns the character set used in this response.
0209: **/
0210: public String getCharacterSet() {
0211: if (_characterSet == null) {
0212: readContentTypeHeader();
0213: if (_characterSet == null)
0214: setCharacterSet(getHeaderField("Charset"));
0215: if (_characterSet == null)
0216: setCharacterSet(HttpUnitOptions
0217: .getDefaultCharacterSet());
0218: }
0219: return _characterSet;
0220: }
0221:
0222: /**
0223: * Returns a list of new cookie names defined as part of this response.
0224: **/
0225: public String[] getNewCookieNames() {
0226: return getCookieJar().getCookieNames();
0227: }
0228:
0229: /**
0230: * Returns the new cookie value defined as part of this response.
0231: **/
0232: public String getNewCookieValue(String name) {
0233: return getCookieJar().getCookieValue(name);
0234: }
0235:
0236: /**
0237: * Returns the names of the header fields found in the response.
0238: **/
0239: abstract public String[] getHeaderFieldNames();
0240:
0241: /**
0242: * Returns the value for the specified header field. If no such field is defined, will return null.
0243: * If more than one header is defined for the specified name, returns only the first found.
0244: **/
0245: abstract public String getHeaderField(String fieldName);
0246:
0247: /**
0248: * Returns the text of the response (excluding headers) as a string. Use this method in preference to 'toString'
0249: * which may be used to represent internal state of this object.
0250: **/
0251: public String getText() throws IOException {
0252: if (_responseText == null)
0253: loadResponseText();
0254: return _responseText;
0255: }
0256:
0257: /**
0258: * Returns a buffered input stream for reading the contents of this reply.
0259: **/
0260: public InputStream getInputStream() throws IOException {
0261: if (_inputStream == null)
0262: _inputStream = new ByteArrayInputStream(getText()
0263: .getBytes());
0264: return _inputStream;
0265: }
0266:
0267: /**
0268: * Returns the names of the frames found in the page in the order in which they appear.
0269: * @exception SAXException thrown if there is an error parsing this response
0270: **/
0271: public String[] getFrameNames() throws SAXException {
0272: WebFrame[] frames = getFrames();
0273: String[] result = new String[frames.length];
0274: for (int i = 0; i < result.length; i++) {
0275: result[i] = frames[i].getFrameName();
0276: }
0277:
0278: return result;
0279: }
0280:
0281: /**
0282: * Returns the frames found in the page in the order in which they appear.
0283: * @exception SAXException thrown if there is an error parsing this response
0284: **/
0285: FrameSelector[] getFrameSelectors() throws SAXException {
0286: WebFrame[] frames = getFrames();
0287: FrameSelector[] result = new FrameSelector[frames.length];
0288: for (int i = 0; i < result.length; i++) {
0289: result[i] = frames[i].getSelector();
0290: }
0291:
0292: return result;
0293: }
0294:
0295: /**
0296: * Returns the contents of the specified subframe of this frameset response.
0297: *
0298: * @param subFrameName the name of the desired frame as defined in the frameset.
0299: **/
0300: public WebResponse getSubframeContents(String subFrameName) {
0301: if (_window == null)
0302: throw new NoSuchFrameException(subFrameName);
0303: return _window.getSubframeContents(_frame, subFrameName);
0304: }
0305:
0306: //---------------------- HTMLSegment methods -----------------------------
0307:
0308: /**
0309: * Returns the HTMLElement with the specified ID.
0310: * @throws SAXException thrown if there is an error parsing the response.
0311: */
0312: public HTMLElement getElementWithID(String id) throws SAXException {
0313: return getReceivedPage().getElementWithID(id);
0314: }
0315:
0316: /**
0317: * Returns a list of HTML element names contained in this HTML section.
0318: */
0319: public String[] getElementNames() throws SAXException {
0320: return getReceivedPage().getElementNames();
0321: }
0322:
0323: /**
0324: * Returns the HTMLElements found in this segment with the specified name.
0325: */
0326: public HTMLElement[] getElementsWithName(String name)
0327: throws SAXException {
0328: return getReceivedPage().getElementsWithName(name);
0329: }
0330:
0331: /**
0332: * Returns the HTMLElements found with the specified attribute value.
0333: * @since 1.6
0334: */
0335: public HTMLElement[] getElementsWithAttribute(String name,
0336: String value) throws SAXException {
0337: return getReceivedPage().getElementsWithAttribute(name, value);
0338: }
0339:
0340: /**
0341: * Returns the forms found in the page in the order in which they appear.
0342: * @exception SAXException thrown if there is an error parsing the response.
0343: **/
0344: public WebForm[] getForms() throws SAXException {
0345: return getReceivedPage().getForms();
0346: }
0347:
0348: /**
0349: * Returns the form found in the page with the specified name.
0350: * @exception SAXException thrown if there is an error parsing the response.
0351: **/
0352: public WebForm getFormWithName(String name) throws SAXException {
0353: return getReceivedPage().getFormWithName(name);
0354: }
0355:
0356: /**
0357: * Returns the form found in the page with the specified ID.
0358: * @exception SAXException thrown if there is an error parsing the response.
0359: **/
0360: public WebForm getFormWithID(String ID) throws SAXException {
0361: return getReceivedPage().getFormWithID(ID);
0362: }
0363:
0364: /**
0365: * Returns the first form found in the page matching the specified criteria.
0366: * @exception SAXException thrown if there is an error parsing the response.
0367: **/
0368: public WebForm getFirstMatchingForm(HTMLElementPredicate predicate,
0369: Object criteria) throws SAXException {
0370: return getReceivedPage().getFirstMatchingForm(predicate,
0371: criteria);
0372: }
0373:
0374: /**
0375: * Returns all forms found in the page matching the specified criteria.
0376: * @exception SAXException thrown if there is an error parsing the response.
0377: **/
0378: public WebForm[] getMatchingForms(HTMLElementPredicate predicate,
0379: Object criteria) throws SAXException {
0380: return getReceivedPage().getMatchingForms(predicate, criteria);
0381: }
0382:
0383: /**
0384: * Returns the links found in the page in the order in which they appear.
0385: * @exception SAXException thrown if there is an error parsing the response.
0386: **/
0387: public WebLink[] getLinks() throws SAXException {
0388: return getReceivedPage().getLinks();
0389: }
0390:
0391: /**
0392: * Returns the first link which contains the specified text.
0393: * @exception SAXException thrown if there is an error parsing the response.
0394: **/
0395: public WebLink getLinkWith(String text) throws SAXException {
0396: return getReceivedPage().getLinkWith(text);
0397: }
0398:
0399: /**
0400: * Returns the first link which contains an image with the specified text as its 'alt' attribute.
0401: * @exception SAXException thrown if there is an error parsing the response.
0402: **/
0403: public WebLink getLinkWithImageText(String text)
0404: throws SAXException {
0405: return getReceivedPage().getLinkWithImageText(text);
0406: }
0407:
0408: /**
0409: * Returns the link found in the page with the specified name.
0410: * @exception SAXException thrown if there is an error parsing the response.
0411: **/
0412: public WebLink getLinkWithName(String name) throws SAXException {
0413: return getReceivedPage().getLinkWithName(name);
0414: }
0415:
0416: /**
0417: * Returns the link found in the page with the specified ID.
0418: * @exception SAXException thrown if there is an error parsing the response.
0419: **/
0420: public WebLink getLinkWithID(String ID) throws SAXException {
0421: return getReceivedPage().getLinkWithID(ID);
0422: }
0423:
0424: /**
0425: * Returns the first link found in the page matching the specified criteria.
0426: * @exception SAXException thrown if there is an error parsing the response.
0427: **/
0428: public WebLink getFirstMatchingLink(HTMLElementPredicate predicate,
0429: Object criteria) throws SAXException {
0430: return getReceivedPage().getFirstMatchingLink(predicate,
0431: criteria);
0432: }
0433:
0434: /**
0435: * Returns all links found in the page matching the specified criteria.
0436: * @exception SAXException thrown if there is an error parsing the response.
0437: **/
0438: public WebLink[] getMatchingLinks(HTMLElementPredicate predicate,
0439: Object criteria) throws SAXException {
0440: return getReceivedPage().getMatchingLinks(predicate, criteria);
0441: }
0442:
0443: /**
0444: * Returns the images found in the page in the order in which they appear.
0445: * @exception SAXException thrown if there is an error parsing the response.
0446: **/
0447: public WebImage[] getImages() throws SAXException {
0448: return getReceivedPage().getImages();
0449: }
0450:
0451: /**
0452: * Returns the image found in the page with the specified name attribute.
0453: * @exception SAXException thrown if there is an error parsing the response.
0454: **/
0455: public WebImage getImageWithName(String source) throws SAXException {
0456: return getReceivedPage().getImageWithName(source);
0457: }
0458:
0459: /**
0460: * Returns the first image found in the page with the specified src attribute.
0461: * @exception SAXException thrown if there is an error parsing the response.
0462: **/
0463: public WebImage getImageWithSource(String source)
0464: throws SAXException {
0465: return getReceivedPage().getImageWithSource(source);
0466: }
0467:
0468: /**
0469: * Returns the first image found in the page with the specified alt attribute.
0470: **/
0471: public WebImage getImageWithAltText(String altText)
0472: throws SAXException {
0473: return getReceivedPage().getImageWithAltText(altText);
0474: }
0475:
0476: public WebApplet[] getApplets() throws SAXException {
0477: return getReceivedPage().getApplets();
0478: }
0479:
0480: /**
0481: * Returns an array of text blocks found in the page.
0482: * @since 1.6
0483: */
0484: public TextBlock[] getTextBlocks() throws SAXException {
0485: return getReceivedPage().getTextBlocks();
0486: }
0487:
0488: /**
0489: * Returns the text block after the specified block, if any.
0490: * @since 1.6
0491: */
0492: public TextBlock getNextTextBlock(TextBlock block)
0493: throws SAXException {
0494: return getReceivedPage().getNextTextBlock(block);
0495: }
0496:
0497: /**
0498: * Returns the first link found in the page matching the specified criteria.
0499: * @since 1.6
0500: * @exception SAXException thrown if there is an error parsing the response.
0501: **/
0502: public TextBlock getFirstMatchingTextBlock(
0503: HTMLElementPredicate predicate, Object criteria)
0504: throws SAXException {
0505: return getReceivedPage().getFirstMatchingTextBlock(predicate,
0506: criteria);
0507: }
0508:
0509: /**
0510: * Returns a copy of the domain object model tree associated with this response.
0511: * If the response is HTML, it will use a special parser which can transform HTML into an XML DOM.
0512: * @exception SAXException thrown if there is an error parsing the response.
0513: **/
0514: public Document getDOM() throws SAXException {
0515: if (isHTML()) {
0516: return (Document) getReceivedPage().getDOM();
0517: } else {
0518: try {
0519: return HttpUnitUtils.newParser().parse(
0520: new InputSource(new StringReader(getText())));
0521: } catch (IOException e) {
0522: throw new SAXException(e);
0523: }
0524: }
0525: }
0526:
0527: /**
0528: * Returns the top-level tables found in this page in the order in which
0529: * they appear.
0530: * @exception SAXException thrown if there is an error parsing the response.
0531: **/
0532: public WebTable[] getTables() throws SAXException {
0533: return getReceivedPage().getTables();
0534: }
0535:
0536: /**
0537: * Returns the first table in the response which matches the specified predicate and value.
0538: * Will recurse into any nested tables, as needed.
0539: * @return the selected table, or null if none is found
0540: **/
0541: public WebTable getFirstMatchingTable(
0542: HTMLElementPredicate predicate, Object criteria)
0543: throws SAXException {
0544: return getReceivedPage().getFirstMatchingTable(predicate,
0545: criteria);
0546: }
0547:
0548: /**
0549: * Returns all tables found in the page matching the specified criteria.
0550: * @exception SAXException thrown if there is an error parsing the response.
0551: **/
0552: public WebTable[] getMatchingTables(HTMLElementPredicate predicate,
0553: Object criteria) throws SAXException {
0554: return getReceivedPage().getMatchingTables(predicate, criteria);
0555: }
0556:
0557: /**
0558: * Returns the first table in the response which has the specified text as the full text of
0559: * its first non-blank row and non-blank column. Will recurse into any nested tables, as needed.
0560: * Case is ignored.
0561: * @exception SAXException thrown if there is an error parsing the response.
0562: * @return the selected table, or null if none is found
0563: **/
0564: public WebTable getTableStartingWith(String text)
0565: throws SAXException {
0566: return getReceivedPage().getTableStartingWith(text);
0567: }
0568:
0569: /**
0570: * Returns the first table in the response which has the specified text as a prefix of the text of
0571: * its first non-blank row and non-blank column. Will recurse into any nested tables, as needed.
0572: * Case is ignored.
0573: * @exception SAXException thrown if there is an error parsing the response.
0574: * @return the selected table, or null if none is found
0575: **/
0576: public WebTable getTableStartingWithPrefix(String text)
0577: throws SAXException {
0578: return getReceivedPage().getTableStartingWithPrefix(text);
0579: }
0580:
0581: /**
0582: * Returns the first table in the response which has the specified text as its summary attribute.
0583: * Will recurse into any nested tables, as needed.
0584: * Case is ignored.
0585: * @exception SAXException thrown if there is an error parsing the response.
0586: * @return the selected table, or null if none is found
0587: **/
0588: public WebTable getTableWithSummary(String text)
0589: throws SAXException {
0590: return getReceivedPage().getTableWithSummary(text);
0591: }
0592:
0593: /**
0594: * Returns the first table in the response which has the specified text as its ID attribute.
0595: * Will recurse into any nested tables, as needed.
0596: * Case is ignored.
0597: * @exception SAXException thrown if there is an error parsing the response.
0598: * @return the selected table, or null if none is found
0599: **/
0600: public WebTable getTableWithID(String text) throws SAXException {
0601: return getReceivedPage().getTableWithID(text);
0602: }
0603:
0604: //---------------------------------------- JavaScript methods ----------------------------------------
0605:
0606: public Scriptable getScriptableObject() {
0607: if (_scriptable == null)
0608: _scriptable = new Scriptable();
0609: return _scriptable;
0610: }
0611:
0612: public static ScriptableDelegate newDelegate(
0613: String delegateClassName) {
0614: if (delegateClassName.equalsIgnoreCase("Option")) {
0615: return FormControl.newSelectionOption();
0616: } else {
0617: throw new IllegalArgumentException(
0618: "No such scripting class supported: "
0619: + delegateClassName);
0620: }
0621: }
0622:
0623: public class Scriptable extends ScriptableDelegate implements
0624: NamedDelegate {
0625:
0626: public void alert(String message) {
0627: _client.postAlert(message);
0628: }
0629:
0630: public boolean getConfirmationResponse(String message) {
0631: return _client.getConfirmationResponse(message);
0632: }
0633:
0634: public String getUserResponse(String prompt,
0635: String defaultResponse) {
0636: return _client.getUserResponse(prompt, defaultResponse);
0637: }
0638:
0639: public ClientProperties getClientProperties() {
0640: return _client == null ? ClientProperties
0641: .getDefaultProperties() : _client
0642: .getClientProperties();
0643: }
0644:
0645: public HTMLPage.Scriptable getDocument() {
0646: try {
0647: if (!isHTML())
0648: replaceText(BLANK_HTML, HTML_CONTENT);
0649: return getReceivedPage().getScriptableObject();
0650: } catch (SAXException e) {
0651: throw new RuntimeException(e.toString());
0652: }
0653: }
0654:
0655: public Scriptable[] getFrames() throws SAXException {
0656: String[] names = getFrameNames();
0657: Scriptable[] frames = new Scriptable[names.length];
0658: for (int i = 0; i < frames.length; i++) {
0659: frames[i] = getSubframeContents(names[i])
0660: .getScriptableObject();
0661: }
0662: return frames;
0663: }
0664:
0665: public void load() throws SAXException {
0666: if (isHTML()) {
0667: getReceivedPage().getForms(); // TODO be more explicit here - don't care about forms, after all
0668: doEvent(getReceivedPage().getOnLoadEvent());
0669: }
0670: }
0671:
0672: public Scriptable open(String urlString, String name,
0673: String features, boolean replace) throws IOException,
0674: SAXException {
0675: if (urlString == null || urlString.trim().length() == 0)
0676: urlString = "about:";
0677: GetMethodWebRequest request = new GetMethodWebRequest(
0678: getURL(), urlString, _frame, name);
0679: WebResponse response = _window.getResponse(request);
0680: return response == null ? null : response
0681: .getScriptableObject();
0682: }
0683:
0684: public void close() {
0685: if (getFrameName().equals(WebRequest.TOP_FRAME))
0686: _window.close();
0687: }
0688:
0689: /**
0690: * Returns the value of the named property. Will return null if the property does not exist.
0691: **/
0692: public Object get(String propertyName) {
0693: if (propertyName.equals("name")) {
0694: return getName();
0695: } else if (propertyName.equalsIgnoreCase("top")) {
0696: return _window.getFrameContents(WebRequest.TOP_FRAME)
0697: .getScriptableObject();
0698: } else if (propertyName.equalsIgnoreCase("parent")) {
0699: return _window.getParentFrameContents(_frame)
0700: .getScriptableObject();
0701: } else if (propertyName.equalsIgnoreCase("opener")) {
0702: return getFrameName().equals(WebRequest.TOP_FRAME) ? getScriptable(_window
0703: .getOpener())
0704: : null;
0705: } else if (propertyName.equalsIgnoreCase("closed")) {
0706: return (getFrameName().equals(WebRequest.TOP_FRAME) && _window
0707: .isClosed()) ? Boolean.TRUE : Boolean.FALSE;
0708: } else {
0709: try {
0710: return getSubframeContents(propertyName)
0711: .getScriptableObject();
0712: } catch (NoSuchFrameException e) {
0713: return super .get(propertyName);
0714: }
0715: }
0716: }
0717:
0718: public String getName() {
0719: String windowName = getFrameName().equals(
0720: WebRequest.TOP_FRAME) ? _window.getName()
0721: : getFrameName();
0722: return windowName.startsWith(WebWindow.NO_NAME) ? ""
0723: : windowName;
0724: }
0725:
0726: private Scriptable getScriptable(WebResponse opener) {
0727: return opener == null ? null : opener.getScriptableObject();
0728: }
0729:
0730: /**
0731: * Sets the value of the named property. Will throw a runtime exception if the property does not exist or
0732: * cannot accept the specified value.
0733: **/
0734: public void set(String propertyName, Object value) {
0735: if (propertyName.equals("name")) {
0736: if (value == null)
0737: value = "";
0738: if (getFrameName().equals(WebRequest.TOP_FRAME)) {
0739: _window.setName(value.toString());
0740: }
0741: } else {
0742: super .set(propertyName, value);
0743: }
0744: }
0745:
0746: public void setLocation(String relativeURL) throws IOException,
0747: SAXException {
0748: getWindow().getResponse(
0749: new GetMethodWebRequest(_pageURL, relativeURL,
0750: _frame.getName()));
0751: }
0752:
0753: public URL getURL() {
0754: return WebResponse.this ._pageURL;
0755: }
0756: }
0757:
0758: //---------------------------------------- Object methods --------------------------------------------
0759:
0760: abstract public String toString();
0761:
0762: //----------------------------------------- protected members -----------------------------------------------
0763:
0764: /**
0765: * Constructs a response object.
0766: * @param frame the frame to hold the response
0767: * @param url the url from which the response was received
0768: **/
0769: protected WebResponse(WebClient client, FrameSelector frame, URL url) {
0770: _client = client;
0771: _baseURL = _pageURL = url;
0772: _baseTarget = frame.getName();
0773: _frame = frame;
0774: }
0775:
0776: /**
0777: * Constructs a response object.
0778: * @param frame the frame to hold the response
0779: * @param url the url from which the response was received
0780: **/
0781: protected WebResponse(WebClient client, FrameSelector frame,
0782: URL url, String text) {
0783: this (client, frame, url);
0784: _responseText = text;
0785: }
0786:
0787: final protected void defineRawInputStream(InputStream inputStream)
0788: throws IOException {
0789: if (_inputStream != null || _responseText != null) {
0790: throw new IllegalStateException(
0791: "Must be called before response text is defined.");
0792: }
0793:
0794: if (encodedUsingGZIP()) {
0795: byte[] compressedData = readFromStream(inputStream,
0796: getContentLength());
0797: _inputStream = new GZIPInputStream(
0798: new ByteArrayInputStream(compressedData));
0799: } else {
0800: _inputStream = inputStream;
0801: }
0802: }
0803:
0804: private boolean encodedUsingGZIP() {
0805: String encoding = getHeaderField("Content-Encoding");
0806: return encoding != null && encoding.indexOf("gzip") >= 0;
0807: }
0808:
0809: /**
0810: * Overwrites the current value (if any) of the content type header.
0811: **/
0812: protected void setContentTypeHeader(String value) {
0813: _contentHeader = value;
0814: }
0815:
0816: //------------------------------------------ package members ------------------------------------------------
0817:
0818: final static String BLANK_HTML = "";
0819:
0820: final static WebResponse createBlankResponse() {
0821: return new DefaultWebResponse(BLANK_HTML);
0822: }
0823:
0824: WebWindow getWindow() {
0825: return _window;
0826: }
0827:
0828: void setWindow(WebWindow window) {
0829: _window = window;
0830: }
0831:
0832: boolean replaceText(String text, String contentType) {
0833: if (_parsingPage)
0834: return false;
0835: _responseText = text;
0836: _inputStream = null;
0837: _page = null;
0838: _contentType = contentType;
0839: _baseURL = null;
0840: _baseTarget = _frame.getName();
0841: _refreshHeader = null;
0842:
0843: try {
0844: readTags(text.getBytes());
0845: } catch (UnsupportedEncodingException e) {
0846: throw new RuntimeException(
0847: "Failure while attempting to reparse text: " + e);
0848: } catch (MalformedURLException e) {
0849: throw new RuntimeException(
0850: "Failure while attempting to reparse text: " + e);
0851: }
0852: return true;
0853: }
0854:
0855: /**
0856: * Returns the frames found in the page in the order in which they appear.
0857: **/
0858: WebRequest[] getFrameRequests() throws SAXException {
0859: WebFrame[] frames = getFrames();
0860: Vector requests = new Vector();
0861: for (int i = 0; i < frames.length; i++) {
0862: if (frames[i].hasInitialRequest()) {
0863: requests.addElement(frames[i].getInitialRequest());
0864: }
0865: }
0866:
0867: WebRequest[] result = new WebRequest[requests.size()];
0868: requests.copyInto(result);
0869: return result;
0870: }
0871:
0872: //--------------------------------- private members --------------------------------------
0873:
0874: private WebWindow _window;
0875:
0876: private HTMLPage _page;
0877:
0878: private String _contentHeader;
0879:
0880: private int _contentLength = UNINITIALIZED_INT;
0881:
0882: private String _contentType;
0883:
0884: private String _characterSet;
0885:
0886: private WebRequest _refreshRequest;
0887:
0888: private int _refreshDelay = -1; // initialized to invalid value
0889:
0890: private String _responseText;
0891:
0892: private InputStream _inputStream;
0893:
0894: private final URL _pageURL;
0895:
0896: private final WebClient _client;
0897:
0898: private Scriptable _scriptable;
0899:
0900: protected void loadResponseText() throws IOException {
0901: if (_responseText != null)
0902: throw new IllegalStateException(
0903: "May only invoke loadResponseText once");
0904: _responseText = "";
0905:
0906: InputStream inputStream = getInputStream();
0907: try {
0908: final int contentLength = this .encodedUsingGZIP() ? -1
0909: : getContentLength();
0910: int bytesRemaining = contentLength < 0 ? Integer.MAX_VALUE
0911: : contentLength;
0912: byte[] bytes = readFromStream(inputStream, bytesRemaining);
0913:
0914: readTags(bytes);
0915: _responseText = new String(bytes, getCharacterSet());
0916: _inputStream = new ByteArrayInputStream(bytes);
0917:
0918: if (HttpUnitOptions.isCheckContentLength()
0919: && contentLength >= 0
0920: && bytes.length != contentLength) {
0921: throw new IOException(
0922: "Truncated message. Expected length: "
0923: + contentLength + ", Actual length: "
0924: + bytes.length);
0925: }
0926: } finally {
0927: inputStream.close();
0928: }
0929: }
0930:
0931: private byte[] readFromStream(InputStream inputStream, int maxBytes)
0932: throws IOException {
0933: ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
0934: byte[] buffer = new byte[8 * 1024];
0935: int count = 0;
0936: if (maxBytes > 0) {
0937: do {
0938: outputStream.write(buffer, 0, count);
0939: maxBytes -= count;
0940: if (maxBytes <= 0)
0941: break;
0942: count = inputStream.read(buffer, 0, Math.min(maxBytes,
0943: buffer.length));
0944: } while (count != -1);
0945: } else {
0946: do {
0947: outputStream.write(buffer, 0, count);
0948: int available = getAvailableBytes(inputStream);
0949: count = (available == 0) ? -1 : inputStream.read(
0950: buffer, 0, buffer.length);
0951: } while (count != -1);
0952: }
0953:
0954: byte[] bytes = outputStream.toByteArray();
0955: return bytes;
0956: }
0957:
0958: private int getAvailableBytes(InputStream inputStream)
0959: throws IOException {
0960: int timeLeft = UNKNOWN_LENGTH_TIMEOUT;
0961: int available;
0962: do {
0963: timeLeft -= UNKNOWN_LENGTH_RETRY_INTERVAL;
0964: try {
0965: Thread.sleep(UNKNOWN_LENGTH_RETRY_INTERVAL);
0966: } catch (InterruptedException e) {
0967: }
0968: available = inputStream.available();
0969: } while (available == 0 && timeLeft > 0);
0970: return available;
0971: }
0972:
0973: private void readTags(byte[] rawMessage)
0974: throws UnsupportedEncodingException, MalformedURLException {
0975: ByteTagParser parser = new ByteTagParser(rawMessage);
0976: ByteTag tag = parser.getNextTag();
0977: while (tag != null) {
0978: if (tag.getName().equalsIgnoreCase("meta"))
0979: processMetaTag(tag);
0980: if (tag.getName().equalsIgnoreCase("base"))
0981: processBaseTag(tag);
0982: if (tag.getName().equalsIgnoreCase("noscript")
0983: && HttpUnitOptions.isScriptingEnabled()) {
0984: do {
0985: tag = parser.getNextTag();
0986: } while (tag.getName().equalsIgnoreCase("/noscript"));
0987: }
0988: tag = parser.getNextTag();
0989: }
0990: }
0991:
0992: private void processBaseTag(ByteTag tag)
0993: throws MalformedURLException {
0994: if (tag.getAttribute("href") != null)
0995: _baseURL = new URL(getURL(), tag.getAttribute("href"));
0996: if (tag.getAttribute("target") != null)
0997: _baseTarget = tag.getAttribute("target");
0998: }
0999:
1000: private void processMetaTag(ByteTag tag) {
1001: if (isHttpEquivMetaTag(tag, "content-type")) {
1002: inferContentType(tag.getAttribute("content"));
1003: } else if (isHttpEquivMetaTag(tag, "refresh")) {
1004: inferRefreshHeader(tag.getAttribute("content"));
1005: }
1006: }
1007:
1008: private boolean isHttpEquivMetaTag(ByteTag tag, String headerName) {
1009: return headerName.equalsIgnoreCase(tag
1010: .getAttribute("http_equiv"))
1011: || headerName.equalsIgnoreCase(tag
1012: .getAttribute("http-equiv"));
1013: }
1014:
1015: private void inferRefreshHeader(String refreshHeader) {
1016: String originalHeader = getHeaderField("Refresh");
1017: if (originalHeader == null) {
1018: _refreshHeader = refreshHeader;
1019: }
1020: }
1021:
1022: private void readRefreshRequest() {
1023: if (_refreshDelay >= 0)
1024: return;
1025: _refreshDelay = 0;
1026: String refreshHeader = _refreshHeader != null ? _refreshHeader
1027: : getHeaderField("Refresh");
1028: if (refreshHeader == null)
1029: return;
1030:
1031: int semicolonIndex = refreshHeader.indexOf(';');
1032: if (semicolonIndex < 0) {
1033: interpretRefreshHeaderElement(refreshHeader, refreshHeader);
1034: } else {
1035: interpretRefreshHeaderElement(refreshHeader.substring(0,
1036: semicolonIndex), refreshHeader);
1037: interpretRefreshHeaderElement(refreshHeader
1038: .substring(semicolonIndex + 1), refreshHeader);
1039: }
1040: if (_refreshRequest == null)
1041: _refreshRequest = new GetMethodWebRequest(_pageURL,
1042: _pageURL.toString(), _frame.getName());
1043: }
1044:
1045: private void interpretRefreshHeaderElement(String token,
1046: String refreshHeader) {
1047: if (token.length() == 0)
1048: return;
1049: try {
1050: if (Character.isDigit(token.charAt(0))) {
1051: _refreshDelay = Integer.parseInt(token);
1052: } else {
1053: _refreshRequest = new GetMethodWebRequest(_pageURL,
1054: getRefreshURL(token), _frame.getName());
1055: }
1056: } catch (NumberFormatException e) {
1057: System.out.println("Unable to interpret refresh tag: \""
1058: + refreshHeader + '"');
1059: }
1060: }
1061:
1062: private String getRefreshURL(String text) {
1063: text = text.trim();
1064: if (!text.toUpperCase().startsWith("URL")) {
1065: return HttpUnitUtils.stripQuotes(text);
1066: } else {
1067: int splitIndex = text.indexOf('=');
1068: String value = text.substring(splitIndex + 1).trim();
1069: return HttpUnitUtils.replaceEntities(HttpUnitUtils
1070: .stripQuotes(value));
1071: }
1072: }
1073:
1074: private void inferContentType(String contentTypeHeader) {
1075: String originalHeader = getHeaderField("Content-type");
1076: if (originalHeader == null
1077: || originalHeader.indexOf("charset") < 0) {
1078: setContentTypeHeader(contentTypeHeader);
1079: }
1080: }
1081:
1082: CookieJar getCookieJar() {
1083: if (_cookies == null)
1084: _cookies = new CookieJar(this );
1085: return _cookies;
1086: }
1087:
1088: private CookieJar _cookies;
1089:
1090: private void readContentTypeHeader() {
1091: String contentHeader = (_contentHeader != null) ? _contentHeader
1092: : getHeaderField("Content-type");
1093: if (contentHeader == null) {
1094: _contentType = HttpUnitOptions.getDefaultContentType();
1095: setCharacterSet(HttpUnitOptions.getDefaultCharacterSet());
1096: _contentHeader = _contentType + ";charset=" + _characterSet;
1097: } else {
1098: String[] parts = HttpUnitUtils
1099: .parseContentTypeHeader(contentHeader);
1100: _contentType = parts[0];
1101: if (parts[1] != null)
1102: setCharacterSet(parts[1]);
1103: }
1104: }
1105:
1106: private WebFrame[] getFrames() throws SAXException {
1107: return getReceivedPage().getFrames();
1108:
1109: }
1110:
1111: HTMLPage getReceivedPage() throws SAXException {
1112: if (_page == null) {
1113: try {
1114: _parsingPage = true;
1115: if (!isHTML())
1116: throw new NotHTMLException(getContentType());
1117: _page = new HTMLPage(this , _frame, _baseURL,
1118: _baseTarget, getCharacterSet());
1119: _page.parse(getText(), _pageURL);
1120: if (_page == null)
1121: throw new IllegalStateException(
1122: "replaceText called in the middle of getReceivedPage()");
1123: } catch (IOException e) {
1124: e.printStackTrace();
1125: throw new RuntimeException(e.toString());
1126: } finally {
1127: _parsingPage = false;
1128: }
1129: }
1130: return _page;
1131: }
1132:
1133: private static String _defaultEncoding;
1134:
1135: private final static String[] DEFAULT_ENCODING_CANDIDATES = {
1136: HttpUnitUtils.DEFAULT_CHARACTER_SET, "us-ascii", "utf-8",
1137: "utf8" };
1138:
1139: static String getDefaultEncoding() {
1140: if (_defaultEncoding == null) {
1141: for (int i = 0; i < DEFAULT_ENCODING_CANDIDATES.length; i++) {
1142: try {
1143: _defaultEncoding = DEFAULT_ENCODING_CANDIDATES[i];
1144: "abcd".getBytes(_defaultEncoding); // throws an exception if the encoding is not supported
1145: return _defaultEncoding;
1146: } catch (UnsupportedEncodingException e) {
1147: }
1148: }
1149: }
1150: return (_defaultEncoding = System.getProperty("file.encoding"));
1151: }
1152:
1153: private void setCharacterSet(String characterSet) {
1154: if (characterSet == null)
1155: return;
1156:
1157: try {
1158: "abcd".getBytes(characterSet);
1159: _characterSet = characterSet;
1160: } catch (UnsupportedEncodingException e) {
1161: _characterSet = getDefaultEncoding();
1162: }
1163: }
1164:
1165: void setCookie(String name, String value) {
1166: _client.putCookie(name, value);
1167: }
1168:
1169: String getCookieHeader() {
1170: return _client.getCookieJar().getCookieHeaderField(getURL());
1171: }
1172:
1173: String getReferer() {
1174: return null;
1175: }
1176:
1177: //=======================================================================================
1178:
1179: static class ByteTag {
1180:
1181: ByteTag(byte[] buffer, int start, int length)
1182: throws UnsupportedEncodingException {
1183: _buffer = new String(buffer, start, length, WebResponse
1184: .getDefaultEncoding()).toCharArray();
1185: _name = nextToken();
1186:
1187: String attribute = "";
1188: String token = nextToken();
1189: while (token.length() != 0) {
1190: if (token.equals("=") && attribute.length() != 0) {
1191: getAttributes().put(attribute.toLowerCase(),
1192: nextToken());
1193: attribute = "";
1194: } else {
1195: if (attribute.length() > 0)
1196: getAttributes()
1197: .put(attribute.toLowerCase(), "");
1198: attribute = token;
1199: }
1200: token = nextToken();
1201: }
1202: }
1203:
1204: public String getName() {
1205: return _name;
1206: }
1207:
1208: public String getAttribute(String attributeName) {
1209: return (String) getAttributes().get(attributeName);
1210: }
1211:
1212: public String toString() {
1213: return "ByteTag[ name=" + _name + ";attributes = "
1214: + _attributes + ']';
1215: }
1216:
1217: private Hashtable getAttributes() {
1218: if (_attributes == null)
1219: _attributes = new Hashtable();
1220: return _attributes;
1221: }
1222:
1223: private String _name = "";
1224: private Hashtable _attributes;
1225:
1226: private char[] _buffer;
1227: private int _start;
1228: private int _end = -1;
1229:
1230: private String nextToken() {
1231: _start = _end + 1;
1232: while (_start < _buffer.length
1233: && Character.isWhitespace(_buffer[_start]))
1234: _start++;
1235: if (_start >= _buffer.length) {
1236: return "";
1237: } else if (_buffer[_start] == '"') {
1238: for (_end = _start + 1; _end < _buffer.length
1239: && _buffer[_end] != '"'; _end++)
1240: ;
1241: return new String(_buffer, _start + 1, _end - _start
1242: - 1);
1243: } else if (_buffer[_start] == '\'') {
1244: for (_end = _start + 1; _end < _buffer.length
1245: && _buffer[_end] != '\''; _end++)
1246: ;
1247: return new String(_buffer, _start + 1, _end - _start
1248: - 1);
1249: } else if (_buffer[_start] == '=') {
1250: _end = _start;
1251: return "=";
1252: } else {
1253: for (_end = _start + 1; _end < _buffer.length
1254: && _buffer[_end] != '='
1255: && !Character.isWhitespace(_buffer[_end]); _end++)
1256: ;
1257: return new String(_buffer, _start, (_end--) - _start);
1258: }
1259: }
1260: }
1261:
1262: //=======================================================================================
1263:
1264: static class ByteTagParser {
1265: ByteTagParser(byte[] buffer) {
1266: _buffer = buffer;
1267: }
1268:
1269: ByteTag getNextTag() throws UnsupportedEncodingException {
1270: ByteTag byteTag = null;
1271: do {
1272: _start = _end + 1;
1273: while (_start < _buffer.length
1274: && _buffer[_start] != '<')
1275: _start++;
1276: for (_end = _start + 1; _end < _buffer.length
1277: && _buffer[_end] != '>'; _end++)
1278: ;
1279: if (_end >= _buffer.length || _end < _start)
1280: return null;
1281: byteTag = new ByteTag(_buffer, _start + 1, _end
1282: - _start - 1);
1283: if (byteTag.getName().equalsIgnoreCase("script")) {
1284: _scriptDepth++;
1285: return byteTag;
1286: }
1287: if (byteTag.getName().equalsIgnoreCase("/script"))
1288: _scriptDepth--;
1289: } while (_scriptDepth > 0);
1290: return byteTag;
1291: }
1292:
1293: private int _scriptDepth = 0;
1294: private int _start = 0;
1295: private int _end = -1;
1296:
1297: private byte[] _buffer;
1298: }
1299:
1300: }
1301:
1302: //=======================================================================================
1303:
1304: class DefaultWebResponse extends WebResponse {
1305:
1306: DefaultWebResponse(String text) {
1307: this (null, null, text);
1308: }
1309:
1310: DefaultWebResponse(WebClient client, URL url, String text) {
1311: this (client, FrameSelector.TOP_FRAME, url, text);
1312: }
1313:
1314: DefaultWebResponse(WebClient client, FrameSelector frame, URL url,
1315: String text) {
1316: super (client, frame, url, text);
1317: }
1318:
1319: /**
1320: * Returns the response code associated with this response.
1321: **/
1322: public int getResponseCode() {
1323: return HttpURLConnection.HTTP_OK;
1324: }
1325:
1326: /**
1327: * Returns the response message associated with this response.
1328: **/
1329: public String getResponseMessage() {
1330: return "OK";
1331: }
1332:
1333: public String[] getHeaderFieldNames() {
1334: return new String[] { "Content-type" };
1335: }
1336:
1337: /**
1338: * Returns the value for the specified header field. If no such field is defined, will return null.
1339: **/
1340: public String getHeaderField(String fieldName) {
1341: if (fieldName.equalsIgnoreCase("Content-type")) {
1342: return "text/html; charset=us-ascii";
1343: } else {
1344: return null;
1345: }
1346: }
1347:
1348: public String[] getHeaderFields(String fieldName) {
1349: String value = getHeaderField(fieldName);
1350: return value == null ? new String[0] : new String[] { value };
1351: }
1352:
1353: public String toString() {
1354: try {
1355: return "DefaultWebResponse [" + getText() + "]";
1356: } catch (IOException e) { // should never happen
1357: return "DefaultWebResponse [???]";
1358: }
1359: }
1360: }
|