• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / httpunit / 755

14 Feb 2026 07:14PM UTC coverage: 80.526%. Remained the same
755

push

github

hazendaz
[ci] Fix badge

3213 of 4105 branches covered (78.27%)

Branch coverage included in aggregate %.

8245 of 10124 relevant lines covered (81.44%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.88
/src/main/java/com/meterware/httpunit/WebResponse.java
1
/*
2
 * SPDX-License-Identifier: MIT
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2000-2026 Russell Gold
6
 * Copyright 2021-2000 hazendaz
7
 */
8
package com.meterware.httpunit;
9

10
import com.meterware.httpunit.cookies.CookieJar;
11
import com.meterware.httpunit.cookies.CookieSource;
12
import com.meterware.httpunit.dom.DomWindow;
13
import com.meterware.httpunit.dom.DomWindowProxy;
14
import com.meterware.httpunit.dom.HTMLDocumentImpl;
15
import com.meterware.httpunit.dom.HTMLElementImpl;
16
import com.meterware.httpunit.protocol.MessageBody;
17
import com.meterware.httpunit.scripting.NamedDelegate;
18
import com.meterware.httpunit.scripting.ScriptableDelegate;
19
import com.meterware.httpunit.scripting.ScriptingHandler;
20

21
import java.io.ByteArrayInputStream;
22
import java.io.ByteArrayOutputStream;
23
import java.io.EOFException;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.io.StringReader;
27
import java.net.HttpURLConnection;
28
import java.net.MalformedURLException;
29
import java.net.URL;
30
import java.net.URLConnection;
31
import java.nio.charset.Charset;
32
import java.nio.charset.StandardCharsets;
33
import java.nio.charset.UnsupportedCharsetException;
34
import java.util.ArrayList;
35
import java.util.Hashtable;
36
import java.util.List;
37
import java.util.Locale;
38
import java.util.zip.GZIPInputStream;
39

40
import org.w3c.dom.Document;
41
import org.w3c.dom.Node;
42
import org.xml.sax.InputSource;
43
import org.xml.sax.SAXException;
44

45
/**
46
 * A response to a web request from a web server.
47
 **/
48
public abstract class WebResponse implements HTMLSegment, CookieSource, DomWindowProxy {
49

50
    /** The Constant HTML_CONTENT. */
51
    private static final String HTML_CONTENT = "text/html";
52

53
    /** The Constant XHTML_CONTENT. */
54
    private static final String XHTML_CONTENT = "application/xhtml+xml";
55

56
    /** The Constant FAUX_XHTML_CONTENT. */
57
    private static final String FAUX_XHTML_CONTENT = "text/xhtml";
58
    // [ 1281655 ] [patch] allow text/xml to be parsed as html
59
    /** The Constant XML_CONTENT. */
60
    // testTraversal test changed after positive reply by Russell
61
    private static final String XML_CONTENT = "text/xml";
62

63
    /** The valid content types. */
64
    // the list of valid content Types
65
    private static String[] validContentTypes = { HTML_CONTENT, XHTML_CONTENT, FAUX_XHTML_CONTENT, XML_CONTENT };
1✔
66

67
    /** The Constant UNINITIALIZED_INT. */
68
    private static final int UNINITIALIZED_INT = -2;
69

70
    /** The Constant UNKNOWN_LENGTH_TIMEOUT. */
71
    private static final int UNKNOWN_LENGTH_TIMEOUT = 500;
72

73
    /** The Constant UNKNOWN_LENGTH_RETRY_INTERVAL. */
74
    private static final int UNKNOWN_LENGTH_RETRY_INTERVAL = 10;
75

76
    /** The frame. */
77
    private FrameSelector _frame;
78

79
    /** The with parse. */
80
    // allow to switch off parsing e.g. for method="HEAD"
81
    private boolean _withParse = true;
1✔
82

83
    /** The base target. */
84
    private String _baseTarget;
85

86
    /** The refresh header. */
87
    private String _refreshHeader;
88

89
    /** The base URL. */
90
    private URL _baseURL;
91

92
    /** The parsing page. */
93
    private boolean _parsingPage;
94

95
    /**
96
     * is parsing on?.
97
     *
98
     * @return true if parsing is enabled
99
     */
100
    public boolean isWithParse() {
101
        return _withParse;
1✔
102
    }
103

104
    /**
105
     * set the parsing switch.
106
     *
107
     * @param doParse
108
     *            the new with parse
109
     */
110
    public void setWithParse(boolean doParse) {
111
        _withParse = doParse;
1✔
112
    }
1✔
113

114
    /**
115
     * Returns a web response built from a URL connection. Provided to allow access to WebResponse parsing without using
116
     * a WebClient.
117
     *
118
     * @param connection
119
     *            the connection
120
     *
121
     * @return the web response
122
     *
123
     * @throws IOException
124
     *             Signals that an I/O exception has occurred.
125
     */
126
    public static WebResponse newResponse(URLConnection connection) throws IOException {
127
        return new HttpWebResponse(null, FrameSelector.TOP_FRAME, connection.getURL(), connection,
×
128
                HttpUnitOptions.getExceptionsThrownOnErrorStatus());
×
129
    }
130

131
    /**
132
     * Returns true if the response is HTML.
133
     *
134
     * @return true if the contenType fits
135
     **/
136
    public boolean isHTML() {
137
        boolean result = false;
1✔
138
        // check the different content types
139
        for (String validContentType : validContentTypes) {
1✔
140
            result = getContentType().equalsIgnoreCase(validContentType);
1✔
141
            if (result) {
1✔
142
                break;
1✔
143
            }
144
        } // for
145
        return result;
1✔
146
    }
147

148
    /**
149
     * Returns the URL which invoked this response.
150
     **/
151
    @Override
152
    public URL getURL() {
153
        return _pageURL;
1✔
154
    }
155

156
    /**
157
     * Returns the title of the page.
158
     *
159
     * @return the title
160
     *
161
     * @exception SAXException
162
     *                thrown if there is an error parsing this response
163
     */
164
    public String getTitle() throws SAXException {
165
        return getReceivedPage().getTitle();
1✔
166
    }
167

168
    /**
169
     * Returns the stylesheet linked in the head of the page. <code> <link type="text/css" rel="stylesheet"
170
     * href="/mystyle.css" /> </code> will return "/mystyle.css".
171
     *
172
     * @return the external style sheet
173
     *
174
     * @exception SAXException
175
     *                thrown if there is an error parsing this response
176
     */
177
    public String getExternalStyleSheet() throws SAXException {
178
        return getReceivedPage().getExternalStyleSheet();
1✔
179
    }
180

181
    /**
182
     * Retrieves the "content" of the meta tags for a key pair attribute-attributeValue. <code> <meta
183
     * name="robots" content="index" /> <meta name="robots" content="follow" /> <meta http-equiv="Expires"
184
     * content="now" /> </code> this can be used like this <code> getMetaTagContent("name","robots") will
185
     * return { "index","follow" } getMetaTagContent("http-equiv","Expires") will return { "now" } </code>
186
     *
187
     * @param attribute
188
     *            the attribute
189
     * @param attributeValue
190
     *            the attribute value
191
     *
192
     * @return the meta tag content
193
     *
194
     * @exception SAXException
195
     *                thrown if there is an error parsing this response
196
     */
197
    public String[] getMetaTagContent(String attribute, String attributeValue) throws SAXException {
198
        return getReceivedPage().getMetaTagContent(attribute, attributeValue);
1✔
199
    }
200

201
    /**
202
     * Returns the name of the frame containing this page.
203
     *
204
     * @return the frame name
205
     */
206
    public String getFrameName() {
207
        return _frame.getName();
1✔
208
    }
209

210
    /**
211
     * Sets the frame.
212
     *
213
     * @param frame
214
     *            the new frame
215
     */
216
    void setFrame(FrameSelector frame) {
217
        if (!_frame.getName().equals(frame.getName())) {
1!
218
            throw new IllegalArgumentException("May not modify the frame name");
×
219
        }
220
        _frame = frame;
1✔
221
    }
1✔
222

223
    /**
224
     * Returns the frame containing this page.
225
     *
226
     * @return the frame
227
     */
228
    FrameSelector getFrame() {
229
        return _frame;
1✔
230
    }
231

232
    /**
233
     * Returns a request to refresh this page, if any. This request will be defined by a meta tag in the header. If no
234
     * tag exists, will return null.
235
     *
236
     * @return the refresh request
237
     */
238
    public WebRequest getRefreshRequest() {
239
        readRefreshRequest();
1✔
240
        return _refreshRequest;
1✔
241
    }
242

243
    /**
244
     * Returns the delay before normally following the request to refresh this page, if any. This request will be
245
     * defined by a meta tag in the header. If no tag exists, will return zero.
246
     *
247
     * @return the refresh delay
248
     */
249
    public int getRefreshDelay() {
250
        readRefreshRequest();
1✔
251
        return _refreshDelay;
1✔
252
    }
253

254
    /**
255
     * Returns the response code associated with this response.
256
     *
257
     * @return the response code
258
     */
259
    public abstract int getResponseCode();
260

261
    /**
262
     * Returns the response message associated with this response.
263
     *
264
     * @return the response message
265
     */
266
    public abstract String getResponseMessage();
267

268
    /**
269
     * Returns the content length of this response.
270
     *
271
     * @return the content length, if known, or -1.
272
     */
273
    public int getContentLength() {
274
        if (_contentLength == UNINITIALIZED_INT) {
1✔
275
            String length = getHeaderField("Content-Length");
1✔
276
            _contentLength = length == null ? -1 : Integer.parseInt(length);
1✔
277
        }
278
        return _contentLength;
1✔
279
    }
280

281
    /**
282
     * Returns the content type of this response.
283
     *
284
     * @return the content type
285
     */
286
    public String getContentType() {
287
        if (_contentType == null) {
1✔
288
            readContentTypeHeader();
1✔
289
        }
290
        return _contentType;
1✔
291
    }
292

293
    /**
294
     * Returns the character set used in this response.
295
     *
296
     * @return the character set
297
     */
298
    public String getCharacterSet() {
299
        if (_characterSet == null) {
1✔
300
            readContentTypeHeader();
1✔
301
            if (_characterSet == null) {
1✔
302
                setCharacterSet(getHeaderField("Charset"));
1✔
303
            }
304
            if (_characterSet == null) {
1✔
305
                setCharacterSet(HttpUnitOptions.getDefaultCharacterSet());
1✔
306
            }
307
        }
308
        return _characterSet;
1✔
309
    }
310

311
    /**
312
     * Returns a list of new cookie names defined as part of this response.
313
     *
314
     * @return the new cookie names
315
     */
316
    public String[] getNewCookieNames() {
317
        return getCookieJar().getCookieNames();
1✔
318
    }
319

320
    /**
321
     * Returns the new cookie value defined as part of this response.
322
     *
323
     * @param name
324
     *            the name
325
     *
326
     * @return the new cookie value
327
     */
328
    public String getNewCookieValue(String name) {
329
        return getCookieJar().getCookieValue(name);
×
330
    }
331

332
    /**
333
     * Returns the names of the header fields found in the response.
334
     *
335
     * @return the header field names
336
     */
337
    public abstract String[] getHeaderFieldNames();
338

339
    /**
340
     * Returns the value for the specified header field. If no such field is defined, will return null. If more than one
341
     * header is defined for the specified name, returns only the first found.
342
     *
343
     * @param fieldName
344
     *            the field name
345
     *
346
     * @return the header field
347
     */
348
    public abstract String getHeaderField(String fieldName);
349

350
    /**
351
     * Returns the actual byte stream of the response e.g. for download results
352
     *
353
     * @return the byte array read for this response
354
     *
355
     * @throws IOException
356
     *             Signals that an I/O exception has occurred.
357
     */
358
    public byte[] getBytes() throws IOException {
359
        if (_responseText == null) {
1✔
360
            loadResponseText();
1✔
361
        }
362
        return _bytes;
1✔
363
    }
364

365
    /**
366
     * Returns the text of the response (excluding headers) as a string. Use this method in preference to 'toString'
367
     * which may be used to represent internal state of this object.
368
     *
369
     * @return the response text
370
     *
371
     * @throws IOException
372
     *             Signals that an I/O exception has occurred.
373
     */
374
    public String getText() throws IOException {
375
        if (_responseText == null) {
1✔
376
            loadResponseText();
1✔
377
        }
378
        return _responseText;
1✔
379
    }
380

381
    /**
382
     * Returns a buffered input stream for reading the contents of this reply.
383
     *
384
     * @return the input stream
385
     *
386
     * @throws IOException
387
     *             Signals that an I/O exception has occurred.
388
     */
389
    public InputStream getInputStream() throws IOException {
390
        if (_inputStream == null) {
1✔
391
            _inputStream = new ByteArrayInputStream(getText().getBytes(StandardCharsets.UTF_8));
1✔
392
        }
393
        return _inputStream;
1✔
394
    }
395

396
    /**
397
     * Returns the names of the frames found in the page in the order in which they appear.
398
     *
399
     * @return the frame names
400
     *
401
     * @exception SAXException
402
     *                thrown if there is an error parsing this response
403
     */
404
    public String[] getFrameNames() throws SAXException {
405
        WebFrame[] frames = getFrames();
1✔
406
        String[] result = new String[frames.length];
1✔
407
        for (int i = 0; i < result.length; i++) {
1✔
408
            result[i] = frames[i].getFrameName();
1✔
409
        }
410

411
        return result;
1✔
412
    }
413

414
    /**
415
     * Returns the frames found in the page in the order in which they appear.
416
     *
417
     * @return the frame selectors
418
     *
419
     * @exception SAXException
420
     *                thrown if there is an error parsing this response
421
     */
422
    FrameSelector[] getFrameSelectors() throws SAXException {
423
        WebFrame[] frames = getFrames();
1✔
424
        FrameSelector[] result = new FrameSelector[frames.length];
1✔
425
        for (int i = 0; i < result.length; i++) {
1✔
426
            result[i] = frames[i].getSelector();
1✔
427
        }
428

429
        return result;
1✔
430
    }
431

432
    /**
433
     * Returns the contents of the specified subframe of this frameset response.
434
     *
435
     * @param subFrameName
436
     *            the name of the desired frame as defined in the frameset.
437
     *
438
     * @return the subframe contents
439
     */
440
    public WebResponse getSubframeContents(String subFrameName) {
441
        if (_window == null) {
1✔
442
            throw new NoSuchFrameException(subFrameName);
1✔
443
        }
444
        return _window.getSubframeContents(_frame, subFrameName);
1✔
445
    }
446

447
    // ---------------------- HTMLSegment methods -----------------------------
448

449
    /**
450
     * Returns the HTMLElement with the specified ID.
451
     *
452
     * @throws SAXException
453
     *             thrown if there is an error parsing the response.
454
     */
455
    @Override
456
    public HTMLElement getElementWithID(String id) throws SAXException {
457
        return getReceivedPage().getElementWithID(id);
1✔
458
    }
459

460
    /**
461
     * return the HTMLElements with the specified tag name.
462
     *
463
     * @param tagName
464
     *            e.g. "div" or "table"
465
     *
466
     * @return a list of all HTMLElements with that tag name
467
     *
468
     * @throws SAXException
469
     *             the SAX exception
470
     */
471
    public HTMLElement[] getElementsByTagName(String tagName) throws SAXException {
472
        return getReceivedPage().getElementsByTagName(getDOM(), tagName);
1✔
473
    }
474

475
    /**
476
     * Returns a list of HTML element names contained in this HTML section.
477
     */
478
    @Override
479
    public String[] getElementNames() throws SAXException {
480
        return getReceivedPage().getElementNames();
1✔
481
    }
482

483
    /**
484
     * Returns the HTMLElements found in this segment with the specified name.
485
     */
486
    @Override
487
    public HTMLElement[] getElementsWithName(String name) throws SAXException {
488
        return getReceivedPage().getElementsWithName(name);
1✔
489
    }
490

491
    /**
492
     * Returns the HTMLElements found in this segment with the specified class.
493
     *
494
     * @param className
495
     *            the class name
496
     *
497
     * @return the elements with class name
498
     *
499
     * @throws SAXException
500
     *             the SAX exception
501
     */
502
    public HTMLElement[] getElementsWithClassName(String className) throws SAXException {
503
        return getReceivedPage().getElementsWithClassName(className);
1✔
504
    }
505

506
    /**
507
     * Returns the HTMLElements found with the specified attribute value.
508
     */
509
    @Override
510
    public HTMLElement[] getElementsWithAttribute(String name, String value) throws SAXException {
511
        return getReceivedPage().getElementsWithAttribute(name, value);
1✔
512
    }
513

514
    /**
515
     * Returns the forms found in the page in the order in which they appear.
516
     *
517
     * @exception SAXException
518
     *                thrown if there is an error parsing the response.
519
     **/
520
    @Override
521
    public WebForm[] getForms() throws SAXException {
522
        return getReceivedPage().getForms();
1✔
523
    }
524

525
    /**
526
     * Returns the form found in the page with the specified name.
527
     *
528
     * @exception SAXException
529
     *                thrown if there is an error parsing the response.
530
     **/
531
    @Override
532
    public WebForm getFormWithName(String name) throws SAXException {
533
        return getReceivedPage().getFormWithName(name);
1✔
534
    }
535

536
    /**
537
     * Returns the form found in the page with the specified ID.
538
     *
539
     * @exception SAXException
540
     *                thrown if there is an error parsing the response.
541
     **/
542
    @Override
543
    public WebForm getFormWithID(String ID) throws SAXException {
544
        return getReceivedPage().getFormWithID(ID);
1✔
545
    }
546

547
    /**
548
     * Returns the first form found in the page matching the specified criteria.
549
     *
550
     * @exception SAXException
551
     *                thrown if there is an error parsing the response.
552
     **/
553
    @Override
554
    public WebForm getFirstMatchingForm(HTMLElementPredicate predicate, Object criteria) throws SAXException {
555
        return getReceivedPage().getFirstMatchingForm(predicate, criteria);
1✔
556
    }
557

558
    /**
559
     * Returns all forms found in the page matching the specified criteria.
560
     *
561
     * @exception SAXException
562
     *                thrown if there is an error parsing the response.
563
     **/
564
    @Override
565
    public WebForm[] getMatchingForms(HTMLElementPredicate predicate, Object criteria) throws SAXException {
566
        return getReceivedPage().getMatchingForms(predicate, criteria);
×
567
    }
568

569
    /**
570
     * Returns the links found in the page in the order in which they appear.
571
     *
572
     * @exception SAXException
573
     *                thrown if there is an error parsing the response.
574
     **/
575
    @Override
576
    public WebLink[] getLinks() throws SAXException {
577
        return getReceivedPage().getLinks();
1✔
578
    }
579

580
    /**
581
     * Returns the first link which contains the specified text.
582
     *
583
     * @exception SAXException
584
     *                thrown if there is an error parsing the response.
585
     **/
586
    @Override
587
    public WebLink getLinkWith(String text) throws SAXException {
588
        return getReceivedPage().getLinkWith(text);
1✔
589
    }
590

591
    /**
592
     * Returns the first link which contains an image with the specified text as its 'alt' attribute.
593
     *
594
     * @exception SAXException
595
     *                thrown if there is an error parsing the response.
596
     **/
597
    @Override
598
    public WebLink getLinkWithImageText(String text) throws SAXException {
599
        return getReceivedPage().getLinkWithImageText(text);
1✔
600
    }
601

602
    /**
603
     * Returns the link found in the page with the specified name.
604
     *
605
     * @param name
606
     *            the name
607
     *
608
     * @return the link with name
609
     *
610
     * @exception SAXException
611
     *                thrown if there is an error parsing the response.
612
     */
613
    public WebLink getLinkWithName(String name) throws SAXException {
614
        return getReceivedPage().getLinkWithName(name);
1✔
615
    }
616

617
    /**
618
     * Returns the link found in the page with the specified ID.
619
     *
620
     * @param ID
621
     *            the id
622
     *
623
     * @return the link with ID
624
     *
625
     * @exception SAXException
626
     *                thrown if there is an error parsing the response.
627
     */
628
    public WebLink getLinkWithID(String ID) throws SAXException {
629
        return getReceivedPage().getLinkWithID(ID);
1✔
630
    }
631

632
    /**
633
     * Returns the first link found in the page matching the specified criteria.
634
     *
635
     * @exception SAXException
636
     *                thrown if there is an error parsing the response.
637
     **/
638
    @Override
639
    public WebLink getFirstMatchingLink(HTMLElementPredicate predicate, Object criteria) throws SAXException {
640
        return getReceivedPage().getFirstMatchingLink(predicate, criteria);
1✔
641
    }
642

643
    /**
644
     * Returns all links found in the page matching the specified criteria.
645
     *
646
     * @exception SAXException
647
     *                thrown if there is an error parsing the response.
648
     **/
649
    @Override
650
    public WebLink[] getMatchingLinks(HTMLElementPredicate predicate, Object criteria) throws SAXException {
651
        return getReceivedPage().getMatchingLinks(predicate, criteria);
1✔
652
    }
653

654
    /**
655
     * Returns the images found in the page in the order in which they appear.
656
     *
657
     * @exception SAXException
658
     *                thrown if there is an error parsing the response.
659
     **/
660
    @Override
661
    public WebImage[] getImages() throws SAXException {
662
        return getReceivedPage().getImages();
1✔
663
    }
664

665
    /**
666
     * Returns the image found in the page with the specified name attribute.
667
     *
668
     * @exception SAXException
669
     *                thrown if there is an error parsing the response.
670
     **/
671
    @Override
672
    public WebImage getImageWithName(String source) throws SAXException {
673
        return getReceivedPage().getImageWithName(source);
1✔
674
    }
675

676
    /**
677
     * Returns the first image found in the page with the specified src attribute.
678
     *
679
     * @exception SAXException
680
     *                thrown if there is an error parsing the response.
681
     **/
682
    @Override
683
    public WebImage getImageWithSource(String source) throws SAXException {
684
        return getReceivedPage().getImageWithSource(source);
1✔
685
    }
686

687
    /**
688
     * Returns the first image found in the page with the specified alt attribute.
689
     **/
690
    @Override
691
    public WebImage getImageWithAltText(String altText) throws SAXException {
692
        return getReceivedPage().getImageWithAltText(altText);
1✔
693
    }
694

695
    @Override
696
    public WebApplet[] getApplets() throws SAXException {
697
        return getReceivedPage().getApplets();
1✔
698
    }
699

700
    /**
701
     * Returns an array of text blocks found in the page.
702
     */
703
    @Override
704
    public TextBlock[] getTextBlocks() throws SAXException {
705
        return getReceivedPage().getTextBlocks();
1✔
706
    }
707

708
    /**
709
     * Returns the text block after the specified block, if any.
710
     *
711
     * @param block
712
     *            the block
713
     *
714
     * @return the next text block
715
     *
716
     * @throws SAXException
717
     *             the SAX exception
718
     */
719
    public TextBlock getNextTextBlock(TextBlock block) throws SAXException {
720
        return getReceivedPage().getNextTextBlock(block);
1✔
721
    }
722

723
    /**
724
     * Returns the first link found in the page matching the specified criteria.
725
     *
726
     * @param predicate
727
     *            the predicate
728
     * @param criteria
729
     *            the criteria
730
     *
731
     * @return the first matching text block
732
     *
733
     * @exception SAXException
734
     *                thrown if there is an error parsing the response.
735
     */
736
    public TextBlock getFirstMatchingTextBlock(HTMLElementPredicate predicate, Object criteria) throws SAXException {
737
        return getReceivedPage().getFirstMatchingTextBlock(predicate, criteria);
1✔
738
    }
739

740
    /**
741
     * Returns a copy of the domain object model tree associated with this response. If the response is HTML, it will
742
     * use a special parser which can transform HTML into an XML DOM.
743
     *
744
     * @return the dom
745
     *
746
     * @exception SAXException
747
     *                thrown if there is an error parsing the response.
748
     */
749
    public Document getDOM() throws SAXException {
750
        if (isHTML()) {
1!
751
            return (Document) getReceivedPage().getDOM();
1✔
752
        }
753
        try {
754
            return HttpUnitUtils.parse(new InputSource(new StringReader(getText())));
×
755
        } catch (IOException e) {
×
756
            throw new SAXException(e);
×
757
        }
758
    }
759

760
    /**
761
     * Returns the top-level tables found in this page in the order in which they appear.
762
     *
763
     * @exception SAXException
764
     *                thrown if there is an error parsing the response.
765
     **/
766
    @Override
767
    public WebTable[] getTables() throws SAXException {
768
        return getReceivedPage().getTables();
1✔
769
    }
770

771
    /**
772
     * Returns the first table in the response which matches the specified predicate and value. Will recurse into any
773
     * nested tables, as needed.
774
     *
775
     * @return the selected table, or null if none is found
776
     **/
777
    @Override
778
    public WebTable getFirstMatchingTable(HTMLElementPredicate predicate, Object criteria) throws SAXException {
779
        return getReceivedPage().getFirstMatchingTable(predicate, criteria);
×
780
    }
781

782
    /**
783
     * Returns all tables found in the page matching the specified criteria.
784
     *
785
     * @exception SAXException
786
     *                thrown if there is an error parsing the response.
787
     **/
788
    @Override
789
    public WebTable[] getMatchingTables(HTMLElementPredicate predicate, Object criteria) throws SAXException {
790
        return getReceivedPage().getMatchingTables(predicate, criteria);
×
791
    }
792

793
    /**
794
     * Returns the first table in the response which has the specified text as the full text of its first non-blank row
795
     * and non-blank column. Will recurse into any nested tables, as needed. Case is ignored.
796
     *
797
     * @exception SAXException
798
     *                thrown if there is an error parsing the response.
799
     *
800
     * @return the selected table, or null if none is found
801
     **/
802
    @Override
803
    public WebTable getTableStartingWith(String text) throws SAXException {
804
        return getReceivedPage().getTableStartingWith(text);
1✔
805
    }
806

807
    /**
808
     * Returns the first table in the response which has the specified text as a prefix of the text of its first
809
     * non-blank row and non-blank column. Will recurse into any nested tables, as needed. Case is ignored.
810
     *
811
     * @exception SAXException
812
     *                thrown if there is an error parsing the response.
813
     *
814
     * @return the selected table, or null if none is found
815
     **/
816
    @Override
817
    public WebTable getTableStartingWithPrefix(String text) throws SAXException {
818
        return getReceivedPage().getTableStartingWithPrefix(text);
1✔
819
    }
820

821
    /**
822
     * Returns the first table in the response which has the specified text as its summary attribute. Will recurse into
823
     * any nested tables, as needed. Case is ignored.
824
     *
825
     * @exception SAXException
826
     *                thrown if there is an error parsing the response.
827
     *
828
     * @return the selected table, or null if none is found
829
     **/
830
    @Override
831
    public WebTable getTableWithSummary(String text) throws SAXException {
832
        return getReceivedPage().getTableWithSummary(text);
1✔
833
    }
834

835
    /**
836
     * Returns the first table in the response which has the specified text as its ID attribute. Will recurse into any
837
     * nested tables, as needed. Case is ignored.
838
     *
839
     * @exception SAXException
840
     *                thrown if there is an error parsing the response.
841
     *
842
     * @return the selected table, or null if none is found
843
     **/
844
    @Override
845
    public WebTable getTableWithID(String text) throws SAXException {
846
        return getReceivedPage().getTableWithID(text);
1✔
847
    }
848

849
    // ---------------------------------------- JavaScript methods ----------------------------------------
850

851
    /**
852
     * get the scriptable object for this WebResponse.
853
     *
854
     * @return the scriptable object
855
     */
856
    public Scriptable getScriptableObject() {
857
        ScriptingHandler result = this.getScriptingHandler();
1✔
858
        if (!(result instanceof Scriptable)) {
1!
859
            throw new RuntimeException(
×
860
                    "getScriptableObject failed for " + result.getClass().getName() + " - not a Scriptable");
×
861
        }
862
        return (Scriptable) result;
1✔
863
    }
864

865
    /**
866
     * Sets the scripting handler.
867
     *
868
     * @param scriptingHandler
869
     *            the new scripting handler
870
     */
871
    public void setScriptingHandler(ScriptingHandler scriptingHandler) {
872
        _scriptingHandler = scriptingHandler;
×
873
    }
×
874

875
    @Override
876
    public ScriptingHandler getScriptingHandler() {
877
        if (_scriptingHandler == null) {
1✔
878
            _scriptingHandler = HttpUnitOptions.getScriptingEngine().createHandler(this);
1✔
879
        }
880
        return _scriptingHandler;
1✔
881
    }
882

883
    /**
884
     * Creates the javascript scripting handler.
885
     *
886
     * @return the scripting handler
887
     */
888
    public ScriptingHandler createJavascriptScriptingHandler() {
889
        return new Scriptable();
1✔
890
    }
891

892
    /**
893
     * create a DOMScriptingHandler.
894
     *
895
     * @return the DOM scripting handler (the window)
896
     */
897
    public ScriptingHandler createDomScriptingHandler() {
898
        if (!isHTML()) {
×
899
            return new DomWindow(this);
×
900
        }
901
        try {
902
            HTMLPage page = this.getReceivedPage();
×
903
            Node rootNode = page.getRootNode();
×
904
            HTMLDocumentImpl document = (HTMLDocumentImpl) rootNode;
×
905
            DomWindow result = document.getWindow();
×
906
            result.setProxy(this);
×
907
            return result;
×
908
        } catch (SAXException e) {
×
909
            return new DomWindow(this);
×
910
        }
911
    }
912

913
    /**
914
     * New delegate.
915
     *
916
     * @param delegateClassName
917
     *            the delegate class name
918
     *
919
     * @return the scriptable delegate
920
     */
921
    public static ScriptableDelegate newDelegate(String delegateClassName) {
922
        if (delegateClassName.equalsIgnoreCase("Option")) {
1!
923
            return FormControl.newSelectionOption();
1✔
924
        }
925
        throw new IllegalArgumentException("No such scripting class supported: " + delegateClassName);
×
926
    }
927

928
    /**
929
     * Gets the document scriptable.
930
     *
931
     * @return the document scriptable
932
     */
933
    HTMLPage.Scriptable getDocumentScriptable() {
934
        return getScriptableObject().getDocument();
1✔
935
    }
936

937
    /**
938
     * open a a new Window with the given name and relative URL
939
     *
940
     * @param name
941
     *            - the name of the window
942
     * @param relativeUrl
943
     *            - the relative URL to be used
944
     *
945
     * @return the WebResponse as a DomWindowProxy
946
     */
947
    @Override
948
    public DomWindowProxy openNewWindow(String name, String relativeUrl) throws IOException, SAXException {
949
        if (relativeUrl == null || relativeUrl.trim().isEmpty()) {
1✔
950
            relativeUrl = "about:";
1✔
951
        }
952
        GetMethodWebRequest request = new GetMethodWebRequest(getURL(), relativeUrl, _frame, name);
1✔
953
        return _window.getResponse(request);
1✔
954
    }
955

956
    @Override
957
    public DomWindowProxy submitRequest(HTMLElementImpl sourceElement, String method, String location, String target,
958
            MessageBody requestBody) throws IOException, SAXException {
959
        if (method.equalsIgnoreCase("get")) {
1!
960
            return getWindow().sendRequest(new GetMethodWebRequest(this, sourceElement, getURL(), location, target));
1✔
961
        }
962
        return null;
×
963
    }
964

965
    @Override
966
    public void close() {
967
        if (getFrameName().equals(WebRequest.TOP_FRAME)) {
1!
968
            _window.close();
1✔
969
        }
970
    }
1✔
971

972
    @Override
973
    public void alert(String message) {
974
        _client.postAlert(message);
1✔
975
    }
1✔
976

977
    @Override
978
    public boolean confirm(String message) {
979
        return _client.getConfirmationResponse(message);
1✔
980
    }
981

982
    @Override
983
    public String prompt(String prompt, String defaultResponse) {
984
        return _client.getUserResponse(prompt, defaultResponse);
1✔
985
    }
986

987
    /**
988
     * Gets the base target.
989
     *
990
     * @return the base target
991
     */
992
    String getBaseTarget() {
993
        return _baseTarget;
1✔
994
    }
995

996
    /**
997
     * The Class Scriptable.
998
     */
999
    public class Scriptable extends ScriptableDelegate implements NamedDelegate {
1✔
1000

1001
        /**
1002
         * Alert user.
1003
         *
1004
         * @param message
1005
         *            the message
1006
         */
1007
        public void alertUser(String message) {
1008
            alert(message);
1✔
1009
        }
1✔
1010

1011
        /**
1012
         * Gets the confirmation response.
1013
         *
1014
         * @param message
1015
         *            the message
1016
         *
1017
         * @return the confirmation response
1018
         */
1019
        public boolean getConfirmationResponse(String message) {
1020
            return confirm(message);
1✔
1021
        }
1022

1023
        /**
1024
         * Gets the user response.
1025
         *
1026
         * @param prompt
1027
         *            the prompt
1028
         * @param defaultResponse
1029
         *            the default response
1030
         *
1031
         * @return the user response
1032
         */
1033
        public String getUserResponse(String prompt, String defaultResponse) {
1034
            return prompt(prompt, defaultResponse);
1✔
1035
        }
1036

1037
        /**
1038
         * Gets the client properties.
1039
         *
1040
         * @return the client properties
1041
         */
1042
        public ClientProperties getClientProperties() {
1043
            return _client == null ? ClientProperties.getDefaultProperties() : _client.getClientProperties();
1✔
1044
        }
1045

1046
        /**
1047
         * Gets the document.
1048
         *
1049
         * @return the document
1050
         */
1051
        public HTMLPage.Scriptable getDocument() {
1052
            try {
1053
                if (!isHTML()) {
1✔
1054
                    replaceText(BLANK_HTML, HTML_CONTENT);
1✔
1055
                }
1056
                return getReceivedPage().getScriptableObject();
1✔
1057
            } catch (SAXException e) {
×
1058
                throw new RuntimeException(e.toString());
×
1059
            }
1060
        }
1061

1062
        /**
1063
         * Gets the frames.
1064
         *
1065
         * @return the frames
1066
         *
1067
         * @throws SAXException
1068
         *             the SAX exception
1069
         */
1070
        public Scriptable[] getFrames() throws SAXException {
1071
            String[] names = getFrameNames();
1✔
1072
            Scriptable[] frames = new Scriptable[names.length];
1✔
1073
            for (int i = 0; i < frames.length; i++) {
1✔
1074
                frames[i] = getSubframeContents(names[i]).getScriptableObject();
1✔
1075
            }
1076
            return frames;
1✔
1077
        }
1078

1079
        /**
1080
         * Load.
1081
         *
1082
         * @throws SAXException
1083
         *             the SAX exception
1084
         */
1085
        public void load() throws SAXException {
1086
            if (isHTML() && isWithParse()) {
1!
1087
                getReceivedPage().getForms(); // TODO be more explicit here - don't care about forms, after all
1✔
1088
                doEventScript(getReceivedPage().getOnLoadEvent());
1✔
1089
            }
1090
        }
1✔
1091

1092
        /**
1093
         * Open.
1094
         *
1095
         * @param urlString
1096
         *            the url string
1097
         * @param name
1098
         *            the name
1099
         * @param features
1100
         *            the features
1101
         * @param replace
1102
         *            the replace
1103
         *
1104
         * @return the scriptable
1105
         *
1106
         * @throws IOException
1107
         *             Signals that an I/O exception has occurred.
1108
         * @throws SAXException
1109
         *             the SAX exception
1110
         */
1111
        public Scriptable open(String urlString, String name, String features, boolean replace)
1112
                throws IOException, SAXException {
1113
            WebResponse response = (WebResponse) openNewWindow(name, urlString);
1✔
1114
            return response == null ? null : response.getScriptableObject();
1✔
1115
        }
1116

1117
        /**
1118
         * Close window.
1119
         */
1120
        public void closeWindow() {
1121
            close();
1✔
1122
        }
1✔
1123

1124
        /**
1125
         * Returns the value of the named property. Will return null if the property does not exist.
1126
         **/
1127
        @Override
1128
        public Object get(String propertyName) {
1129
            if (propertyName.equals("name")) {
1✔
1130
                return getName();
1✔
1131
            }
1132
            if (propertyName.equalsIgnoreCase("top")) {
1✔
1133
                return _window.getFrameContents(WebRequest.TOP_FRAME).getScriptableObject();
1✔
1134
            }
1135
            if (propertyName.equalsIgnoreCase("parent")) {
1✔
1136
                return _window.getParentFrameContents(_frame).getScriptableObject();
1✔
1137
            }
1138
            if (propertyName.equalsIgnoreCase("opener")) {
1✔
1139
                return getFrameName().equals(WebRequest.TOP_FRAME) ? getScriptable(_window.getOpener()) : null;
1!
1140
            }
1141
            if (propertyName.equalsIgnoreCase("closed")) {
1✔
1142
                return getFrameName().equals(WebRequest.TOP_FRAME) && _window.isClosed() ? Boolean.TRUE : Boolean.FALSE;
1!
1143
            }
1144
            try {
1145
                return getSubframeContents(propertyName).getScriptableObject();
1✔
1146
            } catch (NoSuchFrameException e) {
1✔
1147
                return super.get(propertyName);
1✔
1148
            }
1149
        }
1150

1151
        @Override
1152
        public String getName() {
1153
            String windowName = getFrameName().equals(WebRequest.TOP_FRAME) ? _window.getName() : getFrameName();
1✔
1154
            return windowName.startsWith(WebWindow.NO_NAME) ? "" : windowName;
1✔
1155
        }
1156

1157
        /**
1158
         * Gets the scriptable.
1159
         *
1160
         * @param opener
1161
         *            the opener
1162
         *
1163
         * @return the scriptable
1164
         */
1165
        private Scriptable getScriptable(WebResponse opener) {
1166
            return opener == null ? null : opener.getScriptableObject();
1✔
1167
        }
1168

1169
        /**
1170
         * Sets the value of the named property. Will throw a runtime exception if the property does not exist or cannot
1171
         * accept the specified value.
1172
         **/
1173
        @Override
1174
        public void set(String propertyName, Object value) {
1175
            if (propertyName.equals("name")) {
1!
1176
                if (value == null) {
1!
1177
                    value = "";
×
1178
                }
1179
                if (getFrameName().equals(WebRequest.TOP_FRAME)) {
1!
1180
                    _window.setName(value.toString());
1✔
1181
                }
1182
            } else {
1183
                super.set(propertyName, value);
×
1184
            }
1185
        }
1✔
1186

1187
        /**
1188
         * Sets the location.
1189
         *
1190
         * @param relativeURL
1191
         *            the new location
1192
         *
1193
         * @throws IOException
1194
         *             Signals that an I/O exception has occurred.
1195
         * @throws SAXException
1196
         *             the SAX exception
1197
         */
1198
        public void setLocation(String relativeURL) throws IOException, SAXException {
1199
            getWindow().getResponse(new GetMethodWebRequest(_pageURL, relativeURL, _frame.getName()));
1✔
1200
        }
1✔
1201

1202
        /**
1203
         * Gets the url.
1204
         *
1205
         * @return the url
1206
         */
1207
        public URL getURL() {
1208
            return WebResponse.this._pageURL;
1✔
1209
        }
1210
    }
1211

1212
    // ---------------------------------------- Object methods --------------------------------------------
1213

1214
    @Override
1215
    public abstract String toString();
1216

1217
    // ----------------------------------------- protected members -----------------------------------------------
1218

1219
    /**
1220
     * Constructs a response object. see [ 1159858 ] patch for RFE 1159844 (parsing intercepted pages)
1221
     *
1222
     * @param client
1223
     *            the client
1224
     * @param frame
1225
     *            the frame to hold the response
1226
     * @param url
1227
     *            the url from which the response was received
1228
     */
1229
    protected WebResponse(WebClient client, FrameSelector frame, URL url) {
1✔
1230
        _client = client;
1✔
1231
        _baseURL = _pageURL = url;
1✔
1232
        _baseTarget = frame.getName();
1✔
1233
        _frame = frame;
1✔
1234
        // intialize window for interception as described in
1235
        // https://sourceforge.net/tracker/index.php?func=detail&aid=1159844&group_id=6550&atid=356550
1236
        if (client != null) {
1✔
1237
            _window = client.getMainWindow();
1✔
1238
        }
1239
    }
1✔
1240

1241
    /**
1242
     * Constructs a response object.
1243
     *
1244
     * @param client
1245
     *            the client
1246
     * @param frame
1247
     *            the frame to hold the response
1248
     * @param url
1249
     *            the url from which the response was received
1250
     * @param text
1251
     *            the text
1252
     */
1253
    protected WebResponse(WebClient client, FrameSelector frame, URL url, String text) {
1254
        this(client, frame, url);
1✔
1255
        _responseText = text;
1✔
1256
    }
1✔
1257

1258
    /**
1259
     * Define raw input stream.
1260
     *
1261
     * @param inputStream
1262
     *            the input stream
1263
     *
1264
     * @throws IOException
1265
     *             Signals that an I/O exception has occurred.
1266
     */
1267
    protected final void defineRawInputStream(InputStream inputStream) throws IOException {
1268
        if (_inputStream != null || _responseText != null) {
1!
1269
            throw new IllegalStateException("Must be called before response text is defined.");
×
1270
        }
1271

1272
        // please note bug report [ 1119205 ] EOFExceptions while using a Proxy
1273
        // and patch proposal below
1274
        // by Ralf Bust
1275
        /*
1276
         * original 1.6.2 code if (encodedUsingGZIP()) { byte[] compressedData = readFromStream( inputStream,
1277
         * getContentLength() ); _inputStream = new GZIPInputStream( new ByteArrayInputStream( compressedData ) ); }
1278
         * else { _inputStream = inputStream; }
1279
         */
1280

1281
        if (encodedUsingGZIP()) {
1✔
1282
            try {
1283
                _inputStream = new GZIPInputStream(inputStream);
1✔
1284
            } catch (EOFException eof) {
×
1285
                _inputStream = inputStream;
×
1286
            }
1✔
1287
        } else {
1288
            _inputStream = inputStream;
1✔
1289
        }
1290
    }
1✔
1291

1292
    /**
1293
     * Encoded using GZIP.
1294
     *
1295
     * @return true, if successful
1296
     */
1297
    private boolean encodedUsingGZIP() {
1298
        String encoding = getHeaderField("Content-Encoding");
1✔
1299
        return encoding != null && encoding.indexOf("gzip") >= 0;
1!
1300
    }
1301

1302
    /**
1303
     * Overwrites the current value (if any) of the content type header.
1304
     *
1305
     * @param value
1306
     *            the new content type header
1307
     */
1308
    protected void setContentTypeHeader(String value) {
1309
        _contentHeader = value;
1✔
1310
    }
1✔
1311

1312
    // ------------------------------------------ package members ------------------------------------------------
1313

1314
    /** The Constant BLANK_HTML. */
1315
    static final String BLANK_HTML = "";
1316

1317
    /**
1318
     * Creates the blank response.
1319
     *
1320
     * @return the web response
1321
     */
1322
    static WebResponse createBlankResponse() {
1323
        return new DefaultWebResponse(BLANK_HTML);
1✔
1324
    }
1325

1326
    /**
1327
     * Gets the window.
1328
     *
1329
     * @return the window
1330
     */
1331
    WebWindow getWindow() {
1332
        return _window;
1✔
1333
    }
1334

1335
    /**
1336
     * Sets the window.
1337
     *
1338
     * @param window
1339
     *            the new window
1340
     */
1341
    void setWindow(WebWindow window) {
1342
        _window = window;
1✔
1343
    }
1✔
1344

1345
    /**
1346
     * replace the given text
1347
     *
1348
     * @param text
1349
     *            - the text to replace
1350
     * @param contentType
1351
     *            - the contenttype
1352
     */
1353
    @Override
1354
    public boolean replaceText(String text, String contentType) {
1355
        if (_parsingPage) {
1✔
1356
            return false;
1✔
1357
        }
1358
        _responseText = text;
1✔
1359
        _inputStream = null;
1✔
1360
        _page = null;
1✔
1361
        _contentType = contentType;
1✔
1362
        _baseURL = null;
1✔
1363
        _baseTarget = _frame.getName();
1✔
1364
        _refreshHeader = null;
1✔
1365

1366
        try {
1367
            readTags(text.getBytes(StandardCharsets.UTF_8));
1✔
1368
        } catch (MalformedURLException e) {
×
1369
            throw new RuntimeException("Failure while attempting to reparse text: " + e);
×
1370
        }
1✔
1371
        return true;
1✔
1372
    }
1373

1374
    /**
1375
     * Returns the frames found in the page in the order in which they appear.
1376
     *
1377
     * @return the frame requests
1378
     *
1379
     * @throws SAXException
1380
     *             the SAX exception
1381
     */
1382
    WebRequest[] getFrameRequests() throws SAXException {
1383
        WebFrame[] frames = getFrames();
1✔
1384
        List<WebRequest> requests = new ArrayList<>();
1✔
1385
        for (WebFrame frame : frames) {
1✔
1386
            if (frame.hasInitialRequest()) {
1✔
1387
                requests.add(frame.getInitialRequest());
1✔
1388
            }
1389
        }
1390

1391
        return requests.toArray(new WebRequest[requests.size()]);
1✔
1392
    }
1393

1394
    // --------------------------------- private members --------------------------------------
1395

1396
    /** The window. */
1397
    private WebWindow _window;
1398

1399
    /** The page. */
1400
    private HTMLPage _page;
1401

1402
    /** The content header. */
1403
    private String _contentHeader;
1404

1405
    /** The content length. */
1406
    private int _contentLength = UNINITIALIZED_INT;
1✔
1407

1408
    /** The content type. */
1409
    private String _contentType;
1410

1411
    /** The character set. */
1412
    private String _characterSet;
1413

1414
    /** The refresh request. */
1415
    private WebRequest _refreshRequest;
1416

1417
    /** The refresh delay. */
1418
    private int _refreshDelay = -1; // initialized to invalid value
1✔
1419

1420
    /** the response as a String. */
1421
    private String _responseText;
1422

1423
    /** the response as a byte array. */
1424
    private byte[] _bytes;
1425

1426
    /** The input stream. */
1427
    private InputStream _inputStream;
1428

1429
    /** The page URL. */
1430
    private final URL _pageURL;
1431

1432
    /** The client. */
1433
    private final WebClient _client;
1434

1435
    /**
1436
     * getter for the WebClient.
1437
     *
1438
     * @return the web client for this WebResponse (if any)
1439
     */
1440
    public WebClient getClient() {
1441
        return _client;
×
1442
    }
1443

1444
    /** The scripting handler. */
1445
    private ScriptingHandler _scriptingHandler;
1446

1447
    /**
1448
     * Load response text.
1449
     *
1450
     * @throws IOException
1451
     *             Signals that an I/O exception has occurred.
1452
     */
1453
    protected void loadResponseText() throws IOException {
1454
        if (_responseText != null) {
1!
1455
            throw new IllegalStateException("May only invoke loadResponseText once");
×
1456
        }
1457
        _responseText = "";
1✔
1458

1459
        try (InputStream inputStream = getInputStream()) {
1✔
1460
            final int contentLength = this.encodedUsingGZIP() ? -1 : getContentLength();
1✔
1461
            int bytesRemaining = contentLength < 0 ? Integer.MAX_VALUE : contentLength;
1✔
1462
            _bytes = readFromStream(inputStream, bytesRemaining);
1✔
1463

1464
            readTags(_bytes);
1✔
1465
            _responseText = new String(_bytes, Charset.forName(getCharacterSet()));
1✔
1466
            _inputStream = new ByteArrayInputStream(_bytes);
1✔
1467

1468
            if (HttpUnitOptions.isCheckContentLength() && contentLength >= 0 && _bytes.length != contentLength) {
1!
1469
                throw new IOException(
×
1470
                        "Truncated message. Expected length: " + contentLength + ", Actual length: " + _bytes.length);
1471
            }
1472
        }
1473
    }
1✔
1474

1475
    /**
1476
     * Read from stream.
1477
     *
1478
     * @param inputStream
1479
     *            the input stream
1480
     * @param maxBytes
1481
     *            the max bytes
1482
     *
1483
     * @return the byte[]
1484
     *
1485
     * @throws IOException
1486
     *             Signals that an I/O exception has occurred.
1487
     */
1488
    private byte[] readFromStream(InputStream inputStream, int maxBytes) throws IOException {
1489
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1✔
1490
        byte[] buffer = new byte[8 * 1024];
1✔
1491
        int count = 0;
1✔
1492
        if (maxBytes > 0) {
1✔
1493
            do {
1494
                outputStream.write(buffer, 0, count);
1✔
1495
                maxBytes -= count;
1✔
1496
                if (maxBytes <= 0) {
1✔
1497
                    break;
1✔
1498
                }
1499
                count = inputStream.read(buffer, 0, Math.min(maxBytes, buffer.length));
1✔
1500
            } while (count != -1);
1✔
1501
        } else {
1502
            do {
1503
                outputStream.write(buffer, 0, count);
1✔
1504
                int available = getAvailableBytes(inputStream);
1✔
1505
                count = available == 0 ? -1 : inputStream.read(buffer, 0, buffer.length);
1!
1506
            } while (count != -1);
1!
1507
        }
1508

1509
        return outputStream.toByteArray();
1✔
1510
    }
1511

1512
    /**
1513
     * Gets the available bytes.
1514
     *
1515
     * @param inputStream
1516
     *            the input stream
1517
     *
1518
     * @return the available bytes
1519
     *
1520
     * @throws IOException
1521
     *             Signals that an I/O exception has occurred.
1522
     */
1523
    private int getAvailableBytes(InputStream inputStream) throws IOException {
1524
        int timeLeft = UNKNOWN_LENGTH_TIMEOUT;
1✔
1525
        int available;
1526
        do {
1527
            timeLeft -= UNKNOWN_LENGTH_RETRY_INTERVAL;
1✔
1528
            try {
1529
                Thread.sleep(UNKNOWN_LENGTH_RETRY_INTERVAL);
1✔
1530
            } catch (InterruptedException e) {
×
1531
                Thread.interrupted();
×
1532
                /* do nothing */
1533
            }
1✔
1534
            available = inputStream.available();
1✔
1535
        } while (available == 0 && timeLeft > 0);
1!
1536
        return available;
1✔
1537
    }
1538

1539
    /**
1540
     * read the tags from the given message.
1541
     *
1542
     * @param rawMessage
1543
     *            the raw message
1544
     *
1545
     * @throws MalformedURLException
1546
     *             the malformed URL exception
1547
     */
1548
    private void readTags(byte[] rawMessage) throws MalformedURLException {
1549
        ByteTagParser parser = new ByteTagParser(rawMessage);
1✔
1550
        ByteTag tag = parser.getNextTag();
1✔
1551
        while (tag != null) {
1✔
1552
            if (tag.getName().equalsIgnoreCase("meta")) {
1✔
1553
                processMetaTag(tag);
1✔
1554
            }
1555
            if (tag.getName().equalsIgnoreCase("base")) {
1✔
1556
                processBaseTag(tag);
1✔
1557
            }
1558
            // loop over a noscript region
1559
            if (tag.getName().equalsIgnoreCase("noscript") && HttpUnitOptions.isScriptingEnabled()) {
1✔
1560
                do {
1561
                    tag = parser.getNextTag();
1✔
1562
                } while (!tag.getName().equalsIgnoreCase("/noscript"));
1✔
1563
            }
1564
            tag = parser.getNextTag();
1✔
1565
        }
1566
    }
1✔
1567

1568
    /**
1569
     * Process base tag.
1570
     *
1571
     * @param tag
1572
     *            the tag
1573
     *
1574
     * @throws MalformedURLException
1575
     *             the malformed URL exception
1576
     */
1577
    private void processBaseTag(ByteTag tag) throws MalformedURLException {
1578
        if (tag.getAttribute("href") != null) {
1✔
1579
            _baseURL = new URL(getURL(), tag.getAttribute("href"));
1✔
1580
        }
1581
        if (tag.getAttribute("target") != null) {
1✔
1582
            _baseTarget = tag.getAttribute("target");
1✔
1583
        }
1584
    }
1✔
1585

1586
    /**
1587
     * process MetaTags based on the tag.
1588
     *
1589
     * @param tag
1590
     *            the tag
1591
     */
1592
    private void processMetaTag(ByteTag tag) {
1593
        if (isHttpEquivMetaTag(tag, "content-type")) {
1✔
1594
            inferContentType(tag.getAttribute("content"));
1✔
1595
        } else if (isHttpEquivMetaTag(tag, "refresh")) {
1✔
1596
            inferRefreshHeader(tag.getAttribute("content"));
1✔
1597
        }
1598
    }
1✔
1599

1600
    /**
1601
     * check whether the given tag is a http equiv meta tag.
1602
     *
1603
     * @param tag
1604
     *            the tag
1605
     * @param headerName
1606
     *            the header name
1607
     *
1608
     * @return true, if is http equiv meta tag
1609
     */
1610
    private boolean isHttpEquivMetaTag(ByteTag tag, String headerName) {
1611
        String equiv1 = tag.getAttribute("http_equiv");
1✔
1612
        String equiv2 = tag.getAttribute("http-equiv");
1✔
1613
        return headerName.equalsIgnoreCase(equiv1) || headerName.equalsIgnoreCase(equiv2);
1✔
1614
    }
1615

1616
    /**
1617
     * infer the refresh Header.
1618
     *
1619
     * @param refreshHeader
1620
     *            the refresh header
1621
     */
1622
    private void inferRefreshHeader(String refreshHeader) {
1623
        String originalHeader = getHeaderField("Refresh");
1✔
1624
        // System.err.println("original='"+originalHeader+"'\nrefreshHeader='"+refreshHeader+"'");
1625
        if (originalHeader == null) {
1!
1626
            _refreshHeader = refreshHeader;
1✔
1627
        }
1628
    }
1✔
1629

1630
    /**
1631
     * read the Refresh Request.
1632
     */
1633
    private void readRefreshRequest() {
1634
        if (_refreshDelay >= 0) {
1✔
1635
            return;
1✔
1636
        }
1637
        _refreshDelay = 0;
1✔
1638
        String refreshHeader = _refreshHeader != null ? _refreshHeader : getHeaderField("Refresh");
1✔
1639
        if (refreshHeader == null) {
1✔
1640
            return;
1✔
1641
        }
1642

1643
        int semicolonIndex = refreshHeader.indexOf(';');
1✔
1644
        if (semicolonIndex < 0) {
1✔
1645
            interpretRefreshHeaderElement(refreshHeader, refreshHeader);
1✔
1646
        } else {
1647
            interpretRefreshHeaderElement(refreshHeader.substring(0, semicolonIndex), refreshHeader);
1✔
1648
            interpretRefreshHeaderElement(refreshHeader.substring(semicolonIndex + 1), refreshHeader);
1✔
1649
        }
1650
        if (_refreshRequest == null) {
1✔
1651
            _refreshRequest = new GetMethodWebRequest(_pageURL, _pageURL.toString(), _frame.getName());
1✔
1652
        }
1653
    }
1✔
1654

1655
    /**
1656
     * Interpret refresh header element.
1657
     *
1658
     * @param token
1659
     *            the token
1660
     * @param refreshHeader
1661
     *            the refresh header
1662
     */
1663
    private void interpretRefreshHeaderElement(String token, String refreshHeader) {
1664
        if (token.isEmpty()) {
1!
1665
            return;
×
1666
        }
1667
        try {
1668
            if (Character.isDigit(token.charAt(0))) {
1✔
1669
                _refreshDelay = Integer.parseInt(token);
1✔
1670
            } else {
1671
                _refreshRequest = new GetMethodWebRequest(_pageURL, getRefreshURL(token), _frame.getName());
1✔
1672
            }
1673
        } catch (NumberFormatException e) {
×
1674
            System.out.println("Unable to interpret refresh tag: \"" + refreshHeader + '"');
×
1675
        }
1✔
1676
    }
1✔
1677

1678
    /**
1679
     * Gets the refresh URL.
1680
     *
1681
     * @param text
1682
     *            the text
1683
     *
1684
     * @return the refresh URL
1685
     */
1686
    private String getRefreshURL(String text) {
1687
        text = text.trim();
1✔
1688
        if (!text.toUpperCase().startsWith("URL")) {
1✔
1689
            return HttpUnitUtils.stripQuotes(text);
1✔
1690
        }
1691
        int splitIndex = text.indexOf('=');
1✔
1692
        String value = text.substring(splitIndex + 1).trim();
1✔
1693
        return HttpUnitUtils.replaceEntities(HttpUnitUtils.stripQuotes(value));
1✔
1694
    }
1695

1696
    /**
1697
     * Infer content type.
1698
     *
1699
     * @param contentTypeHeader
1700
     *            the content type header
1701
     */
1702
    private void inferContentType(String contentTypeHeader) {
1703
        String originalHeader = getHeaderField("Content-type");
1✔
1704
        if (originalHeader == null || originalHeader.indexOf("charset") < 0) {
1!
1705
            setContentTypeHeader(contentTypeHeader);
1✔
1706
        }
1707
    }
1✔
1708

1709
    /**
1710
     * Gets the cookie jar.
1711
     *
1712
     * @return the cookie jar
1713
     */
1714
    CookieJar getCookieJar() {
1715
        if (_cookies == null) {
1✔
1716
            _cookies = new CookieJar(this);
1✔
1717
        }
1718
        return _cookies;
1✔
1719
    }
1720

1721
    /** The cookies. */
1722
    private CookieJar _cookies;
1723

1724
    /**
1725
     * Read content type header.
1726
     */
1727
    private void readContentTypeHeader() {
1728
        String contentHeader = _contentHeader != null ? _contentHeader : getHeaderField("Content-type");
1✔
1729
        if (contentHeader == null) {
1!
1730
            _contentType = HttpUnitOptions.getDefaultContentType();
×
1731
            setCharacterSet(HttpUnitOptions.getDefaultCharacterSet());
×
1732
            _contentHeader = _contentType + ";charset=" + _characterSet;
×
1733
        } else {
1734
            String[] parts = HttpUnitUtils.parseContentTypeHeader(contentHeader);
1✔
1735
            if (null != _client && null != _client.getClientProperties().getOverrideContentType()) {
1✔
1736
                _contentType = _client.getClientProperties().getOverrideContentType();
1✔
1737
            } else {
1738
                _contentType = parts[0];
1✔
1739
            }
1740
            if (parts[1] != null) {
1✔
1741
                setCharacterSet(parts[1]);
1✔
1742
            }
1743
        }
1744
    }
1✔
1745

1746
    /**
1747
     * Gets the frames.
1748
     *
1749
     * @return the frames
1750
     *
1751
     * @throws SAXException
1752
     *             the SAX exception
1753
     */
1754
    private WebFrame[] getFrames() throws SAXException {
1755
        if (isWithParse()) {
1✔
1756
            return getReceivedPage().getFrames();
1✔
1757
        }
1758
        return new WebFrame[0];
1✔
1759
    }
1760

1761
    /**
1762
     * get the received Page.
1763
     *
1764
     * @return the received page
1765
     *
1766
     * @throws SAXException
1767
     *             the SAX exception
1768
     */
1769
    HTMLPage getReceivedPage() throws SAXException {
1770
        if (_page == null) {
1✔
1771
            try {
1772
                _parsingPage = true;
1✔
1773
                if (HttpUnitOptions.isCheckHtmlContentType() && !isHTML()) {
1✔
1774
                    throw new NotHTMLException(getContentType());
1✔
1775
                }
1776
                _page = new HTMLPage(this, _frame, _baseURL, _baseTarget, getCharacterSet());
1✔
1777
                if (_withParse) {
1!
1778
                    _page.parse(getText(), _pageURL);
1✔
1779
                    if (_page == null) {
1!
1780
                        throw new IllegalStateException("replaceText called in the middle of getReceivedPage()");
×
1781
                    }
1782
                    ((HTMLDocumentImpl) _page.getRootNode()).getWindow().setProxy(this);
1✔
1783
                }
1784
            } catch (IOException e) {
×
1785
                HttpUnitUtils.handleException(e);
×
1786
                throw new RuntimeException(e.toString());
×
1787
            } finally {
1788
                _parsingPage = false;
1✔
1789
            }
1790
        }
1791
        return _page;
1✔
1792
    }
1793

1794
    /** The default encoding. */
1795
    private static String _defaultEncoding;
1796

1797
    /** The Constant DEFAULT_ENCODING_CANDIDATES. */
1798
    private static final String[] DEFAULT_ENCODING_CANDIDATES = { StandardCharsets.ISO_8859_1.name(),
1✔
1799
            StandardCharsets.US_ASCII.name() };
1✔
1800

1801
    /**
1802
     * Gets the default encoding.
1803
     *
1804
     * @return the default encoding
1805
     */
1806
    static String getDefaultEncoding() {
1807
        if (_defaultEncoding == null) {
1✔
1808
            for (String element : DEFAULT_ENCODING_CANDIDATES) {
1!
1809
                if (isSupportedCharacterSet(element)) {
1!
1810
                    return _defaultEncoding = element;
1✔
1811
                }
1812
            }
1813
            _defaultEncoding = Charset.defaultCharset().displayName();
×
1814
        }
1815
        return _defaultEncoding;
1✔
1816
    }
1817

1818
    /**
1819
     * Sets the character set.
1820
     *
1821
     * @param characterSet
1822
     *            the new character set
1823
     */
1824
    private void setCharacterSet(String characterSet) {
1825
        if (characterSet == null) {
1✔
1826
            return;
1✔
1827
        }
1828

1829
        _characterSet = isSupportedCharacterSet(characterSet) ? characterSet : getDefaultEncoding();
1✔
1830
    }
1✔
1831

1832
    /**
1833
     * Checks if is supported character set.
1834
     *
1835
     * @param characterSet
1836
     *            the character set
1837
     *
1838
     * @return true, if is supported character set
1839
     */
1840
    private static boolean isSupportedCharacterSet(String characterSet) {
1841
        try {
1842
            return "abcd".getBytes(Charset.forName(characterSet)).length > 0;
1!
1843
        } catch (UnsupportedCharsetException e) {
1✔
1844
            return false;
1✔
1845
        }
1846
    }
1847

1848
    /**
1849
     * Sets the cookie.
1850
     *
1851
     * @param name
1852
     *            the name
1853
     * @param value
1854
     *            the value
1855
     */
1856
    void setCookie(String name, String value) {
1857
        _client.putCookie(name, value);
1✔
1858
    }
1✔
1859

1860
    /**
1861
     * Gets the cookie header.
1862
     *
1863
     * @return the cookie header
1864
     */
1865
    String getCookieHeader() {
1866
        return _client.getCookieJar().getCookieHeaderField(getURL());
1✔
1867
    }
1868

1869
    /**
1870
     * Gets the referer.
1871
     *
1872
     * @return the referer
1873
     */
1874
    String getReferer() {
1875
        return null;
1✔
1876
    }
1877

1878
    // =======================================================================================
1879

1880
    /**
1881
     * The Class ByteTag.
1882
     */
1883
    static class ByteTag {
1884

1885
        /**
1886
         * Instantiates a new byte tag.
1887
         *
1888
         * @param buffer
1889
         *            the buffer
1890
         * @param start
1891
         *            the start
1892
         * @param length
1893
         *            the length
1894
         */
1895
        ByteTag(byte[] buffer, int start, int length) {
1✔
1896
            _buffer = new String(buffer, start, length, Charset.forName(WebResponse.getDefaultEncoding()))
1✔
1897
                    .toCharArray();
1✔
1898
            _name = nextToken();
1✔
1899

1900
            String attribute = "";
1✔
1901
            String token = nextToken();
1✔
1902
            while (!token.isEmpty()) {
1✔
1903
                if (token.equals("=") && !attribute.isEmpty()) {
1✔
1904
                    getAttributes().put(attribute.toLowerCase(Locale.ENGLISH), nextToken());
1✔
1905
                    attribute = "";
1✔
1906
                } else {
1907
                    if (!attribute.isEmpty()) {
1✔
1908
                        getAttributes().put(attribute.toLowerCase(Locale.ENGLISH), "");
1✔
1909
                    }
1910
                    attribute = token;
1✔
1911
                }
1912
                token = nextToken();
1✔
1913
            }
1914
        }
1✔
1915

1916
        /**
1917
         * Gets the name.
1918
         *
1919
         * @return the name
1920
         */
1921
        public String getName() {
1922
            return _name;
1✔
1923
        }
1924

1925
        /**
1926
         * Gets the attribute.
1927
         *
1928
         * @param attributeName
1929
         *            the attribute name
1930
         *
1931
         * @return the attribute
1932
         */
1933
        public String getAttribute(String attributeName) {
1934
            return (String) getAttributes().get(attributeName);
1✔
1935
        }
1936

1937
        @Override
1938
        public String toString() {
1939
            return "ByteTag[ name=" + _name + ";attributes = " + _attributes + ']';
×
1940
        }
1941

1942
        /**
1943
         * Gets the attributes.
1944
         *
1945
         * @return the attributes
1946
         */
1947
        private Hashtable getAttributes() {
1948
            if (_attributes == null) {
1✔
1949
                _attributes = new Hashtable<>();
1✔
1950
            }
1951
            return _attributes;
1✔
1952
        }
1953

1954
        /** The name. */
1955
        private String _name = "";
1✔
1956

1957
        /** The attributes. */
1958
        private Hashtable _attributes;
1959

1960
        /** The buffer. */
1961
        private char[] _buffer;
1962

1963
        /** The end. */
1964
        private int _end = -1;
1✔
1965

1966
        /**
1967
         * Next token.
1968
         *
1969
         * @return the string
1970
         */
1971
        private String nextToken() {
1972
            int start = _end + 1;
1✔
1973
            while (start < _buffer.length && Character.isWhitespace(_buffer[start])) {
1✔
1974
                start++;
1✔
1975
            }
1976
            if (start >= _buffer.length) {
1✔
1977
                return "";
1✔
1978
            }
1979
            if (_buffer[start] == '"') {
1✔
1980
                for (_end = start + 1; _end < _buffer.length && _buffer[_end] != '"'; _end++) {
1✔
1981

1982
                }
1983
                return new String(_buffer, start + 1, _end - start - 1);
1✔
1984
            }
1985
            if (_buffer[start] == '\'') {
1✔
1986
                for (_end = start + 1; _end < _buffer.length && _buffer[_end] != '\''; _end++) {
1✔
1987

1988
                }
1989
                return new String(_buffer, start + 1, _end - start - 1);
1✔
1990
            }
1991
            if (_buffer[start] == '=') {
1✔
1992
                _end = start;
1✔
1993
                return "=";
1✔
1994
            }
1995
            for (_end = start + 1; _end < _buffer.length && _buffer[_end] != '='
1✔
1996
                    && !Character.isWhitespace(_buffer[_end]); _end++) {
1✔
1997

1998
            }
1999
            return new String(_buffer, start, _end-- - start);
1✔
2000
        }
2001
    }
2002

2003
    // =======================================================================================
2004

2005
    /**
2006
     * The Class ByteTagParser.
2007
     */
2008
    static class ByteTagParser {
2009

2010
        /**
2011
         * Instantiates a new byte tag parser.
2012
         *
2013
         * @param buffer
2014
         *            the buffer
2015
         */
2016
        ByteTagParser(byte[] buffer) {
1✔
2017
            _buffer = buffer;
1✔
2018
        }
1✔
2019

2020
        /**
2021
         * Gets the next tag.
2022
         *
2023
         * @return the next tag
2024
         */
2025
        ByteTag getNextTag() {
2026
            ByteTag byteTag = null;
1✔
2027
            do {
2028
                int _start = _end + 1;
1✔
2029
                while (_start < _buffer.length && _buffer[_start] != '<') {
1✔
2030
                    _start++;
1✔
2031
                }
2032
                // proposed patch for bug report
2033
                // [ 1376739 ] iframe tag not recognized if Javascript code contains '<'
2034
                // by Nathan Jakubiak
2035
                // uncommented since it doesn't seem to fix the test in WebFrameTest.java
2036
                // if (_scriptDepth > 0 && _start+1 < _buffer.length &&
2037
                // _buffer[ _start+1 ] != '/') {
2038
                // _end = _start+1;
2039
                // continue;
2040
                // }
2041
                for (_end = _start + 1; _end < _buffer.length && _buffer[_end] != '>'; _end++) {
1✔
2042

2043
                }
2044
                if (_end >= _buffer.length || _end < _start) {
1!
2045
                    return null;
1✔
2046
                }
2047
                byteTag = new ByteTag(_buffer, _start + 1, _end - _start - 1);
1✔
2048
                if (byteTag.getName().equalsIgnoreCase("script")) {
1✔
2049
                    _scriptDepth++;
1✔
2050
                    return byteTag;
1✔
2051
                }
2052
                if (byteTag.getName().equalsIgnoreCase("/script")) {
1✔
2053
                    _scriptDepth--;
1✔
2054
                }
2055
            } while (_scriptDepth > 0);
1✔
2056
            return byteTag;
1✔
2057
        }
2058

2059
        /** The script depth. */
2060
        private int _scriptDepth = 0;
1✔
2061

2062
        /** The end. */
2063
        private int _end = -1;
1✔
2064

2065
        /** The buffer. */
2066
        private byte[] _buffer;
2067
    }
2068

2069
    /**
2070
     * allow access to the valid content Types.
2071
     *
2072
     * @return the validContentTypes
2073
     */
2074
    public static String[] getValidContentTypes() {
2075
        return validContentTypes;
×
2076
    }
2077

2078
    /**
2079
     * allow modification of the valid content Types use with care.
2080
     *
2081
     * @param validContentTypes
2082
     *            the validContentTypes to set
2083
     */
2084
    protected static void setValidContentTypes(String[] validContentTypes) {
2085
        WebResponse.validContentTypes = validContentTypes;
×
2086
    }
×
2087

2088
}
2089

2090
// =======================================================================================
2091

2092
class DefaultWebResponse extends WebResponse {
2093

2094
    DefaultWebResponse(String text) {
2095
        this(null, null, text);
1✔
2096
    }
1✔
2097

2098
    DefaultWebResponse(WebClient client, URL url, String text) {
2099
        this(client, FrameSelector.TOP_FRAME, url, text);
1✔
2100
    }
1✔
2101

2102
    DefaultWebResponse(WebClient client, FrameSelector frame, URL url, String text) {
2103
        super(client, frame, url, text);
1✔
2104
    }
1✔
2105

2106
    /**
2107
     * Returns the response code associated with this response.
2108
     **/
2109
    @Override
2110
    public int getResponseCode() {
2111
        return HttpURLConnection.HTTP_OK;
1✔
2112
    }
2113

2114
    /**
2115
     * Returns the response message associated with this response.
2116
     **/
2117
    @Override
2118
    public String getResponseMessage() {
2119
        return "OK";
×
2120
    }
2121

2122
    @Override
2123
    public String[] getHeaderFieldNames() {
2124
        return new String[] { "Content-type" };
×
2125
    }
2126

2127
    /**
2128
     * Returns the value for the specified header field. If no such field is defined, will return null.
2129
     **/
2130
    @Override
2131
    public String getHeaderField(String fieldName) {
2132
        if (fieldName.equalsIgnoreCase("Content-type")) {
1✔
2133
            return "text/html; charset=us-ascii";
1✔
2134
        }
2135
        return null;
1✔
2136
    }
2137

2138
    @Override
2139
    public String[] getHeaderFields(String fieldName) {
2140
        String value = getHeaderField(fieldName);
1✔
2141
        return value == null ? new String[0] : new String[] { value };
1!
2142
    }
2143

2144
    @Override
2145
    public String toString() {
2146
        try {
2147
            return "DefaultWebResponse [" + getText() + "]";
×
2148
        } catch (IOException e) { // should never happen
×
2149
            return "DefaultWebResponse [???]";
×
2150
        }
2151
    }
2152
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc