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

hazendaz / httpunit / 656

06 Dec 2025 09:11PM UTC coverage: 80.452% (+0.02%) from 80.435%
656

push

github

hazendaz
[maven-release-plugin] prepare for next development iteration

3213 of 4105 branches covered (78.27%)

Branch coverage included in aggregate %.

8245 of 10137 relevant lines covered (81.34%)

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
 * MIT License
3
 *
4
 * Copyright 2011-2025 Russell Gold
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
9
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
12
 * of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
15
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
17
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
 * DEALINGS IN THE SOFTWARE.
19
 */
20
package com.meterware.httpunit;
21

22
import com.meterware.httpunit.cookies.CookieJar;
23
import com.meterware.httpunit.cookies.CookieSource;
24
import com.meterware.httpunit.dom.DomWindow;
25
import com.meterware.httpunit.dom.DomWindowProxy;
26
import com.meterware.httpunit.dom.HTMLDocumentImpl;
27
import com.meterware.httpunit.dom.HTMLElementImpl;
28
import com.meterware.httpunit.protocol.MessageBody;
29
import com.meterware.httpunit.scripting.NamedDelegate;
30
import com.meterware.httpunit.scripting.ScriptableDelegate;
31
import com.meterware.httpunit.scripting.ScriptingHandler;
32

33
import java.io.ByteArrayInputStream;
34
import java.io.ByteArrayOutputStream;
35
import java.io.EOFException;
36
import java.io.IOException;
37
import java.io.InputStream;
38
import java.io.StringReader;
39
import java.net.HttpURLConnection;
40
import java.net.MalformedURLException;
41
import java.net.URL;
42
import java.net.URLConnection;
43
import java.nio.charset.Charset;
44
import java.nio.charset.StandardCharsets;
45
import java.nio.charset.UnsupportedCharsetException;
46
import java.util.ArrayList;
47
import java.util.Hashtable;
48
import java.util.List;
49
import java.util.Locale;
50
import java.util.zip.GZIPInputStream;
51

52
import org.w3c.dom.Document;
53
import org.w3c.dom.Node;
54
import org.xml.sax.InputSource;
55
import org.xml.sax.SAXException;
56

57
/**
58
 * A response to a web request from a web server.
59
 **/
60
public abstract class WebResponse implements HTMLSegment, CookieSource, DomWindowProxy {
61

62
    /** The Constant HTML_CONTENT. */
63
    private static final String HTML_CONTENT = "text/html";
64

65
    /** The Constant XHTML_CONTENT. */
66
    private static final String XHTML_CONTENT = "application/xhtml+xml";
67

68
    /** The Constant FAUX_XHTML_CONTENT. */
69
    private static final String FAUX_XHTML_CONTENT = "text/xhtml";
70
    // [ 1281655 ] [patch] allow text/xml to be parsed as html
71
    /** The Constant XML_CONTENT. */
72
    // testTraversal test changed after positive reply by Russell
73
    private static final String XML_CONTENT = "text/xml";
74

75
    /** The valid content types. */
76
    // the list of valid content Types
77
    private static String[] validContentTypes = { HTML_CONTENT, XHTML_CONTENT, FAUX_XHTML_CONTENT, XML_CONTENT };
1✔
78

79
    /** The Constant UNINITIALIZED_INT. */
80
    private static final int UNINITIALIZED_INT = -2;
81

82
    /** The Constant UNKNOWN_LENGTH_TIMEOUT. */
83
    private static final int UNKNOWN_LENGTH_TIMEOUT = 500;
84

85
    /** The Constant UNKNOWN_LENGTH_RETRY_INTERVAL. */
86
    private static final int UNKNOWN_LENGTH_RETRY_INTERVAL = 10;
87

88
    /** The frame. */
89
    private FrameSelector _frame;
90

91
    /** The with parse. */
92
    // allow to switch off parsing e.g. for method="HEAD"
93
    private boolean _withParse = true;
1✔
94

95
    /** The base target. */
96
    private String _baseTarget;
97

98
    /** The refresh header. */
99
    private String _refreshHeader;
100

101
    /** The base URL. */
102
    private URL _baseURL;
103

104
    /** The parsing page. */
105
    private boolean _parsingPage;
106

107
    /**
108
     * is parsing on?.
109
     *
110
     * @return true if parsing is enabled
111
     */
112
    public boolean isWithParse() {
113
        return _withParse;
1✔
114
    }
115

116
    /**
117
     * set the parsing switch.
118
     *
119
     * @param doParse
120
     *            the new with parse
121
     */
122
    public void setWithParse(boolean doParse) {
123
        _withParse = doParse;
1✔
124
    }
1✔
125

126
    /**
127
     * Returns a web response built from a URL connection. Provided to allow access to WebResponse parsing without using
128
     * a WebClient.
129
     *
130
     * @param connection
131
     *            the connection
132
     *
133
     * @return the web response
134
     *
135
     * @throws IOException
136
     *             Signals that an I/O exception has occurred.
137
     */
138
    public static WebResponse newResponse(URLConnection connection) throws IOException {
139
        return new HttpWebResponse(null, FrameSelector.TOP_FRAME, connection.getURL(), connection,
×
140
                HttpUnitOptions.getExceptionsThrownOnErrorStatus());
×
141
    }
142

143
    /**
144
     * Returns true if the response is HTML.
145
     *
146
     * @return true if the contenType fits
147
     **/
148
    public boolean isHTML() {
149
        boolean result = false;
1✔
150
        // check the different content types
151
        for (String validContentType : validContentTypes) {
1✔
152
            result = getContentType().equalsIgnoreCase(validContentType);
1✔
153
            if (result) {
1✔
154
                break;
1✔
155
            }
156
        } // for
157
        return result;
1✔
158
    }
159

160
    /**
161
     * Returns the URL which invoked this response.
162
     **/
163
    @Override
164
    public URL getURL() {
165
        return _pageURL;
1✔
166
    }
167

168
    /**
169
     * Returns the title of the page.
170
     *
171
     * @return the title
172
     *
173
     * @exception SAXException
174
     *                thrown if there is an error parsing this response
175
     */
176
    public String getTitle() throws SAXException {
177
        return getReceivedPage().getTitle();
1✔
178
    }
179

180
    /**
181
     * Returns the stylesheet linked in the head of the page. <code> <link type="text/css" rel="stylesheet"
182
     * href="/mystyle.css" /> </code> will return "/mystyle.css".
183
     *
184
     * @return the external style sheet
185
     *
186
     * @exception SAXException
187
     *                thrown if there is an error parsing this response
188
     */
189
    public String getExternalStyleSheet() throws SAXException {
190
        return getReceivedPage().getExternalStyleSheet();
1✔
191
    }
192

193
    /**
194
     * Retrieves the "content" of the meta tags for a key pair attribute-attributeValue. <code> <meta
195
     * name="robots" content="index" /> <meta name="robots" content="follow" /> <meta http-equiv="Expires"
196
     * content="now" /> </code> this can be used like this <code> getMetaTagContent("name","robots") will
197
     * return { "index","follow" } getMetaTagContent("http-equiv","Expires") will return { "now" } </code>
198
     *
199
     * @param attribute
200
     *            the attribute
201
     * @param attributeValue
202
     *            the attribute value
203
     *
204
     * @return the meta tag content
205
     *
206
     * @exception SAXException
207
     *                thrown if there is an error parsing this response
208
     */
209
    public String[] getMetaTagContent(String attribute, String attributeValue) throws SAXException {
210
        return getReceivedPage().getMetaTagContent(attribute, attributeValue);
1✔
211
    }
212

213
    /**
214
     * Returns the name of the frame containing this page.
215
     *
216
     * @return the frame name
217
     */
218
    public String getFrameName() {
219
        return _frame.getName();
1✔
220
    }
221

222
    /**
223
     * Sets the frame.
224
     *
225
     * @param frame
226
     *            the new frame
227
     */
228
    void setFrame(FrameSelector frame) {
229
        if (!_frame.getName().equals(frame.getName())) {
1!
230
            throw new IllegalArgumentException("May not modify the frame name");
×
231
        }
232
        _frame = frame;
1✔
233
    }
1✔
234

235
    /**
236
     * Returns the frame containing this page.
237
     *
238
     * @return the frame
239
     */
240
    FrameSelector getFrame() {
241
        return _frame;
1✔
242
    }
243

244
    /**
245
     * Returns a request to refresh this page, if any. This request will be defined by a meta tag in the header. If no
246
     * tag exists, will return null.
247
     *
248
     * @return the refresh request
249
     */
250
    public WebRequest getRefreshRequest() {
251
        readRefreshRequest();
1✔
252
        return _refreshRequest;
1✔
253
    }
254

255
    /**
256
     * Returns the delay before normally following the request to refresh this page, if any. This request will be
257
     * defined by a meta tag in the header. If no tag exists, will return zero.
258
     *
259
     * @return the refresh delay
260
     */
261
    public int getRefreshDelay() {
262
        readRefreshRequest();
1✔
263
        return _refreshDelay;
1✔
264
    }
265

266
    /**
267
     * Returns the response code associated with this response.
268
     *
269
     * @return the response code
270
     */
271
    public abstract int getResponseCode();
272

273
    /**
274
     * Returns the response message associated with this response.
275
     *
276
     * @return the response message
277
     */
278
    public abstract String getResponseMessage();
279

280
    /**
281
     * Returns the content length of this response.
282
     *
283
     * @return the content length, if known, or -1.
284
     */
285
    public int getContentLength() {
286
        if (_contentLength == UNINITIALIZED_INT) {
1✔
287
            String length = getHeaderField("Content-Length");
1✔
288
            _contentLength = length == null ? -1 : Integer.parseInt(length);
1✔
289
        }
290
        return _contentLength;
1✔
291
    }
292

293
    /**
294
     * Returns the content type of this response.
295
     *
296
     * @return the content type
297
     */
298
    public String getContentType() {
299
        if (_contentType == null) {
1✔
300
            readContentTypeHeader();
1✔
301
        }
302
        return _contentType;
1✔
303
    }
304

305
    /**
306
     * Returns the character set used in this response.
307
     *
308
     * @return the character set
309
     */
310
    public String getCharacterSet() {
311
        if (_characterSet == null) {
1✔
312
            readContentTypeHeader();
1✔
313
            if (_characterSet == null) {
1✔
314
                setCharacterSet(getHeaderField("Charset"));
1✔
315
            }
316
            if (_characterSet == null) {
1✔
317
                setCharacterSet(HttpUnitOptions.getDefaultCharacterSet());
1✔
318
            }
319
        }
320
        return _characterSet;
1✔
321
    }
322

323
    /**
324
     * Returns a list of new cookie names defined as part of this response.
325
     *
326
     * @return the new cookie names
327
     */
328
    public String[] getNewCookieNames() {
329
        return getCookieJar().getCookieNames();
1✔
330
    }
331

332
    /**
333
     * Returns the new cookie value defined as part of this response.
334
     *
335
     * @param name
336
     *            the name
337
     *
338
     * @return the new cookie value
339
     */
340
    public String getNewCookieValue(String name) {
341
        return getCookieJar().getCookieValue(name);
×
342
    }
343

344
    /**
345
     * Returns the names of the header fields found in the response.
346
     *
347
     * @return the header field names
348
     */
349
    public abstract String[] getHeaderFieldNames();
350

351
    /**
352
     * Returns the value for the specified header field. If no such field is defined, will return null. If more than one
353
     * header is defined for the specified name, returns only the first found.
354
     *
355
     * @param fieldName
356
     *            the field name
357
     *
358
     * @return the header field
359
     */
360
    public abstract String getHeaderField(String fieldName);
361

362
    /**
363
     * Returns the actual byte stream of the response e.g. for download results
364
     *
365
     * @return the byte array read for this response
366
     *
367
     * @throws IOException
368
     *             Signals that an I/O exception has occurred.
369
     */
370
    public byte[] getBytes() throws IOException {
371
        if (_responseText == null) {
1✔
372
            loadResponseText();
1✔
373
        }
374
        return _bytes;
1✔
375
    }
376

377
    /**
378
     * Returns the text of the response (excluding headers) as a string. Use this method in preference to 'toString'
379
     * which may be used to represent internal state of this object.
380
     *
381
     * @return the response text
382
     *
383
     * @throws IOException
384
     *             Signals that an I/O exception has occurred.
385
     */
386
    public String getText() throws IOException {
387
        if (_responseText == null) {
1✔
388
            loadResponseText();
1✔
389
        }
390
        return _responseText;
1✔
391
    }
392

393
    /**
394
     * Returns a buffered input stream for reading the contents of this reply.
395
     *
396
     * @return the input stream
397
     *
398
     * @throws IOException
399
     *             Signals that an I/O exception has occurred.
400
     */
401
    public InputStream getInputStream() throws IOException {
402
        if (_inputStream == null) {
1✔
403
            _inputStream = new ByteArrayInputStream(getText().getBytes(StandardCharsets.UTF_8));
1✔
404
        }
405
        return _inputStream;
1✔
406
    }
407

408
    /**
409
     * Returns the names of the frames found in the page in the order in which they appear.
410
     *
411
     * @return the frame names
412
     *
413
     * @exception SAXException
414
     *                thrown if there is an error parsing this response
415
     */
416
    public String[] getFrameNames() throws SAXException {
417
        WebFrame[] frames = getFrames();
1✔
418
        String[] result = new String[frames.length];
1✔
419
        for (int i = 0; i < result.length; i++) {
1✔
420
            result[i] = frames[i].getFrameName();
1✔
421
        }
422

423
        return result;
1✔
424
    }
425

426
    /**
427
     * Returns the frames found in the page in the order in which they appear.
428
     *
429
     * @return the frame selectors
430
     *
431
     * @exception SAXException
432
     *                thrown if there is an error parsing this response
433
     */
434
    FrameSelector[] getFrameSelectors() throws SAXException {
435
        WebFrame[] frames = getFrames();
1✔
436
        FrameSelector[] result = new FrameSelector[frames.length];
1✔
437
        for (int i = 0; i < result.length; i++) {
1✔
438
            result[i] = frames[i].getSelector();
1✔
439
        }
440

441
        return result;
1✔
442
    }
443

444
    /**
445
     * Returns the contents of the specified subframe of this frameset response.
446
     *
447
     * @param subFrameName
448
     *            the name of the desired frame as defined in the frameset.
449
     *
450
     * @return the subframe contents
451
     */
452
    public WebResponse getSubframeContents(String subFrameName) {
453
        if (_window == null) {
1✔
454
            throw new NoSuchFrameException(subFrameName);
1✔
455
        }
456
        return _window.getSubframeContents(_frame, subFrameName);
1✔
457
    }
458

459
    // ---------------------- HTMLSegment methods -----------------------------
460

461
    /**
462
     * Returns the HTMLElement with the specified ID.
463
     *
464
     * @throws SAXException
465
     *             thrown if there is an error parsing the response.
466
     */
467
    @Override
468
    public HTMLElement getElementWithID(String id) throws SAXException {
469
        return getReceivedPage().getElementWithID(id);
1✔
470
    }
471

472
    /**
473
     * return the HTMLElements with the specified tag name.
474
     *
475
     * @param tagName
476
     *            e.g. "div" or "table"
477
     *
478
     * @return a list of all HTMLElements with that tag name
479
     *
480
     * @throws SAXException
481
     *             the SAX exception
482
     */
483
    public HTMLElement[] getElementsByTagName(String tagName) throws SAXException {
484
        return getReceivedPage().getElementsByTagName(getDOM(), tagName);
1✔
485
    }
486

487
    /**
488
     * Returns a list of HTML element names contained in this HTML section.
489
     */
490
    @Override
491
    public String[] getElementNames() throws SAXException {
492
        return getReceivedPage().getElementNames();
1✔
493
    }
494

495
    /**
496
     * Returns the HTMLElements found in this segment with the specified name.
497
     */
498
    @Override
499
    public HTMLElement[] getElementsWithName(String name) throws SAXException {
500
        return getReceivedPage().getElementsWithName(name);
1✔
501
    }
502

503
    /**
504
     * Returns the HTMLElements found in this segment with the specified class.
505
     *
506
     * @param className
507
     *            the class name
508
     *
509
     * @return the elements with class name
510
     *
511
     * @throws SAXException
512
     *             the SAX exception
513
     */
514
    public HTMLElement[] getElementsWithClassName(String className) throws SAXException {
515
        return getReceivedPage().getElementsWithClassName(className);
1✔
516
    }
517

518
    /**
519
     * Returns the HTMLElements found with the specified attribute value.
520
     */
521
    @Override
522
    public HTMLElement[] getElementsWithAttribute(String name, String value) throws SAXException {
523
        return getReceivedPage().getElementsWithAttribute(name, value);
1✔
524
    }
525

526
    /**
527
     * Returns the forms found in the page in the order in which they appear.
528
     *
529
     * @exception SAXException
530
     *                thrown if there is an error parsing the response.
531
     **/
532
    @Override
533
    public WebForm[] getForms() throws SAXException {
534
        return getReceivedPage().getForms();
1✔
535
    }
536

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

548
    /**
549
     * Returns the form found in the page with the specified ID.
550
     *
551
     * @exception SAXException
552
     *                thrown if there is an error parsing the response.
553
     **/
554
    @Override
555
    public WebForm getFormWithID(String ID) throws SAXException {
556
        return getReceivedPage().getFormWithID(ID);
1✔
557
    }
558

559
    /**
560
     * Returns the first form found in the page matching the specified criteria.
561
     *
562
     * @exception SAXException
563
     *                thrown if there is an error parsing the response.
564
     **/
565
    @Override
566
    public WebForm getFirstMatchingForm(HTMLElementPredicate predicate, Object criteria) throws SAXException {
567
        return getReceivedPage().getFirstMatchingForm(predicate, criteria);
1✔
568
    }
569

570
    /**
571
     * Returns all forms found in the page matching the specified criteria.
572
     *
573
     * @exception SAXException
574
     *                thrown if there is an error parsing the response.
575
     **/
576
    @Override
577
    public WebForm[] getMatchingForms(HTMLElementPredicate predicate, Object criteria) throws SAXException {
578
        return getReceivedPage().getMatchingForms(predicate, criteria);
×
579
    }
580

581
    /**
582
     * Returns the links found in the page in the order in which they appear.
583
     *
584
     * @exception SAXException
585
     *                thrown if there is an error parsing the response.
586
     **/
587
    @Override
588
    public WebLink[] getLinks() throws SAXException {
589
        return getReceivedPage().getLinks();
1✔
590
    }
591

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

603
    /**
604
     * Returns the first link which contains an image with the specified text as its 'alt' attribute.
605
     *
606
     * @exception SAXException
607
     *                thrown if there is an error parsing the response.
608
     **/
609
    @Override
610
    public WebLink getLinkWithImageText(String text) throws SAXException {
611
        return getReceivedPage().getLinkWithImageText(text);
1✔
612
    }
613

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

629
    /**
630
     * Returns the link found in the page with the specified ID.
631
     *
632
     * @param ID
633
     *            the id
634
     *
635
     * @return the link with ID
636
     *
637
     * @exception SAXException
638
     *                thrown if there is an error parsing the response.
639
     */
640
    public WebLink getLinkWithID(String ID) throws SAXException {
641
        return getReceivedPage().getLinkWithID(ID);
1✔
642
    }
643

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

655
    /**
656
     * Returns all links found in the page matching the specified criteria.
657
     *
658
     * @exception SAXException
659
     *                thrown if there is an error parsing the response.
660
     **/
661
    @Override
662
    public WebLink[] getMatchingLinks(HTMLElementPredicate predicate, Object criteria) throws SAXException {
663
        return getReceivedPage().getMatchingLinks(predicate, criteria);
1✔
664
    }
665

666
    /**
667
     * Returns the images found in the page in the order in which they appear.
668
     *
669
     * @exception SAXException
670
     *                thrown if there is an error parsing the response.
671
     **/
672
    @Override
673
    public WebImage[] getImages() throws SAXException {
674
        return getReceivedPage().getImages();
1✔
675
    }
676

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

688
    /**
689
     * Returns the first image found in the page with the specified src attribute.
690
     *
691
     * @exception SAXException
692
     *                thrown if there is an error parsing the response.
693
     **/
694
    @Override
695
    public WebImage getImageWithSource(String source) throws SAXException {
696
        return getReceivedPage().getImageWithSource(source);
1✔
697
    }
698

699
    /**
700
     * Returns the first image found in the page with the specified alt attribute.
701
     **/
702
    @Override
703
    public WebImage getImageWithAltText(String altText) throws SAXException {
704
        return getReceivedPage().getImageWithAltText(altText);
1✔
705
    }
706

707
    @Override
708
    public WebApplet[] getApplets() throws SAXException {
709
        return getReceivedPage().getApplets();
1✔
710
    }
711

712
    /**
713
     * Returns an array of text blocks found in the page.
714
     */
715
    @Override
716
    public TextBlock[] getTextBlocks() throws SAXException {
717
        return getReceivedPage().getTextBlocks();
1✔
718
    }
719

720
    /**
721
     * Returns the text block after the specified block, if any.
722
     *
723
     * @param block
724
     *            the block
725
     *
726
     * @return the next text block
727
     *
728
     * @throws SAXException
729
     *             the SAX exception
730
     */
731
    public TextBlock getNextTextBlock(TextBlock block) throws SAXException {
732
        return getReceivedPage().getNextTextBlock(block);
1✔
733
    }
734

735
    /**
736
     * Returns the first link found in the page matching the specified criteria.
737
     *
738
     * @param predicate
739
     *            the predicate
740
     * @param criteria
741
     *            the criteria
742
     *
743
     * @return the first matching text block
744
     *
745
     * @exception SAXException
746
     *                thrown if there is an error parsing the response.
747
     */
748
    public TextBlock getFirstMatchingTextBlock(HTMLElementPredicate predicate, Object criteria) throws SAXException {
749
        return getReceivedPage().getFirstMatchingTextBlock(predicate, criteria);
1✔
750
    }
751

752
    /**
753
     * Returns a copy of the domain object model tree associated with this response. If the response is HTML, it will
754
     * use a special parser which can transform HTML into an XML DOM.
755
     *
756
     * @return the dom
757
     *
758
     * @exception SAXException
759
     *                thrown if there is an error parsing the response.
760
     */
761
    public Document getDOM() throws SAXException {
762
        if (isHTML()) {
1!
763
            return (Document) getReceivedPage().getDOM();
1✔
764
        }
765
        try {
766
            return HttpUnitUtils.parse(new InputSource(new StringReader(getText())));
×
767
        } catch (IOException e) {
×
768
            throw new SAXException(e);
×
769
        }
770
    }
771

772
    /**
773
     * Returns the top-level tables found in this page in the order in which they appear.
774
     *
775
     * @exception SAXException
776
     *                thrown if there is an error parsing the response.
777
     **/
778
    @Override
779
    public WebTable[] getTables() throws SAXException {
780
        return getReceivedPage().getTables();
1✔
781
    }
782

783
    /**
784
     * Returns the first table in the response which matches the specified predicate and value. Will recurse into any
785
     * nested tables, as needed.
786
     *
787
     * @return the selected table, or null if none is found
788
     **/
789
    @Override
790
    public WebTable getFirstMatchingTable(HTMLElementPredicate predicate, Object criteria) throws SAXException {
791
        return getReceivedPage().getFirstMatchingTable(predicate, criteria);
×
792
    }
793

794
    /**
795
     * Returns all tables found in the page matching the specified criteria.
796
     *
797
     * @exception SAXException
798
     *                thrown if there is an error parsing the response.
799
     **/
800
    @Override
801
    public WebTable[] getMatchingTables(HTMLElementPredicate predicate, Object criteria) throws SAXException {
802
        return getReceivedPage().getMatchingTables(predicate, criteria);
×
803
    }
804

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

819
    /**
820
     * Returns the first table in the response which has the specified text as a prefix of the text of its first
821
     * non-blank row and non-blank column. Will recurse into any nested tables, as needed. Case is ignored.
822
     *
823
     * @exception SAXException
824
     *                thrown if there is an error parsing the response.
825
     *
826
     * @return the selected table, or null if none is found
827
     **/
828
    @Override
829
    public WebTable getTableStartingWithPrefix(String text) throws SAXException {
830
        return getReceivedPage().getTableStartingWithPrefix(text);
1✔
831
    }
832

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

847
    /**
848
     * Returns the first table in the response which has the specified text as its ID attribute. Will recurse into any
849
     * nested tables, as needed. Case is ignored.
850
     *
851
     * @exception SAXException
852
     *                thrown if there is an error parsing the response.
853
     *
854
     * @return the selected table, or null if none is found
855
     **/
856
    @Override
857
    public WebTable getTableWithID(String text) throws SAXException {
858
        return getReceivedPage().getTableWithID(text);
1✔
859
    }
860

861
    // ---------------------------------------- JavaScript methods ----------------------------------------
862

863
    /**
864
     * get the scriptable object for this WebResponse.
865
     *
866
     * @return the scriptable object
867
     */
868
    public Scriptable getScriptableObject() {
869
        ScriptingHandler result = this.getScriptingHandler();
1✔
870
        if (!(result instanceof Scriptable)) {
1!
871
            throw new RuntimeException(
×
872
                    "getScriptableObject failed for " + result.getClass().getName() + " - not a Scriptable");
×
873
        }
874
        return (Scriptable) result;
1✔
875
    }
876

877
    /**
878
     * Sets the scripting handler.
879
     *
880
     * @param scriptingHandler
881
     *            the new scripting handler
882
     */
883
    public void setScriptingHandler(ScriptingHandler scriptingHandler) {
884
        _scriptingHandler = scriptingHandler;
×
885
    }
×
886

887
    @Override
888
    public ScriptingHandler getScriptingHandler() {
889
        if (_scriptingHandler == null) {
1✔
890
            _scriptingHandler = HttpUnitOptions.getScriptingEngine().createHandler(this);
1✔
891
        }
892
        return _scriptingHandler;
1✔
893
    }
894

895
    /**
896
     * Creates the javascript scripting handler.
897
     *
898
     * @return the scripting handler
899
     */
900
    public ScriptingHandler createJavascriptScriptingHandler() {
901
        return new Scriptable();
1✔
902
    }
903

904
    /**
905
     * create a DOMScriptingHandler.
906
     *
907
     * @return the DOM scripting handler (the window)
908
     */
909
    public ScriptingHandler createDomScriptingHandler() {
910
        if (!isHTML()) {
×
911
            return new DomWindow(this);
×
912
        }
913
        try {
914
            HTMLPage page = this.getReceivedPage();
×
915
            Node rootNode = page.getRootNode();
×
916
            HTMLDocumentImpl document = (HTMLDocumentImpl) rootNode;
×
917
            DomWindow result = document.getWindow();
×
918
            result.setProxy(this);
×
919
            return result;
×
920
        } catch (SAXException e) {
×
921
            return new DomWindow(this);
×
922
        }
923
    }
924

925
    /**
926
     * New delegate.
927
     *
928
     * @param delegateClassName
929
     *            the delegate class name
930
     *
931
     * @return the scriptable delegate
932
     */
933
    public static ScriptableDelegate newDelegate(String delegateClassName) {
934
        if (delegateClassName.equalsIgnoreCase("Option")) {
1!
935
            return FormControl.newSelectionOption();
1✔
936
        }
937
        throw new IllegalArgumentException("No such scripting class supported: " + delegateClassName);
×
938
    }
939

940
    /**
941
     * Gets the document scriptable.
942
     *
943
     * @return the document scriptable
944
     */
945
    HTMLPage.Scriptable getDocumentScriptable() {
946
        return getScriptableObject().getDocument();
1✔
947
    }
948

949
    /**
950
     * open a a new Window with the given name and relative URL
951
     *
952
     * @param name
953
     *            - the name of the window
954
     * @param relativeUrl
955
     *            - the relative URL to be used
956
     *
957
     * @return the WebResponse as a DomWindowProxy
958
     */
959
    @Override
960
    public DomWindowProxy openNewWindow(String name, String relativeUrl) throws IOException, SAXException {
961
        if (relativeUrl == null || relativeUrl.trim().isEmpty()) {
1✔
962
            relativeUrl = "about:";
1✔
963
        }
964
        GetMethodWebRequest request = new GetMethodWebRequest(getURL(), relativeUrl, _frame, name);
1✔
965
        return _window.getResponse(request);
1✔
966
    }
967

968
    @Override
969
    public DomWindowProxy submitRequest(HTMLElementImpl sourceElement, String method, String location, String target,
970
            MessageBody requestBody) throws IOException, SAXException {
971
        if (method.equalsIgnoreCase("get")) {
1!
972
            return getWindow().sendRequest(new GetMethodWebRequest(this, sourceElement, getURL(), location, target));
1✔
973
        }
974
        return null;
×
975
    }
976

977
    @Override
978
    public void close() {
979
        if (getFrameName().equals(WebRequest.TOP_FRAME)) {
1!
980
            _window.close();
1✔
981
        }
982
    }
1✔
983

984
    @Override
985
    public void alert(String message) {
986
        _client.postAlert(message);
1✔
987
    }
1✔
988

989
    @Override
990
    public boolean confirm(String message) {
991
        return _client.getConfirmationResponse(message);
1✔
992
    }
993

994
    @Override
995
    public String prompt(String prompt, String defaultResponse) {
996
        return _client.getUserResponse(prompt, defaultResponse);
1✔
997
    }
998

999
    /**
1000
     * Gets the base target.
1001
     *
1002
     * @return the base target
1003
     */
1004
    String getBaseTarget() {
1005
        return _baseTarget;
1✔
1006
    }
1007

1008
    /**
1009
     * The Class Scriptable.
1010
     */
1011
    public class Scriptable extends ScriptableDelegate implements NamedDelegate {
1✔
1012

1013
        /**
1014
         * Alert user.
1015
         *
1016
         * @param message
1017
         *            the message
1018
         */
1019
        public void alertUser(String message) {
1020
            alert(message);
1✔
1021
        }
1✔
1022

1023
        /**
1024
         * Gets the confirmation response.
1025
         *
1026
         * @param message
1027
         *            the message
1028
         *
1029
         * @return the confirmation response
1030
         */
1031
        public boolean getConfirmationResponse(String message) {
1032
            return confirm(message);
1✔
1033
        }
1034

1035
        /**
1036
         * Gets the user response.
1037
         *
1038
         * @param prompt
1039
         *            the prompt
1040
         * @param defaultResponse
1041
         *            the default response
1042
         *
1043
         * @return the user response
1044
         */
1045
        public String getUserResponse(String prompt, String defaultResponse) {
1046
            return prompt(prompt, defaultResponse);
1✔
1047
        }
1048

1049
        /**
1050
         * Gets the client properties.
1051
         *
1052
         * @return the client properties
1053
         */
1054
        public ClientProperties getClientProperties() {
1055
            return _client == null ? ClientProperties.getDefaultProperties() : _client.getClientProperties();
1✔
1056
        }
1057

1058
        /**
1059
         * Gets the document.
1060
         *
1061
         * @return the document
1062
         */
1063
        public HTMLPage.Scriptable getDocument() {
1064
            try {
1065
                if (!isHTML()) {
1✔
1066
                    replaceText(BLANK_HTML, HTML_CONTENT);
1✔
1067
                }
1068
                return getReceivedPage().getScriptableObject();
1✔
1069
            } catch (SAXException e) {
×
1070
                throw new RuntimeException(e.toString());
×
1071
            }
1072
        }
1073

1074
        /**
1075
         * Gets the frames.
1076
         *
1077
         * @return the frames
1078
         *
1079
         * @throws SAXException
1080
         *             the SAX exception
1081
         */
1082
        public Scriptable[] getFrames() throws SAXException {
1083
            String[] names = getFrameNames();
1✔
1084
            Scriptable[] frames = new Scriptable[names.length];
1✔
1085
            for (int i = 0; i < frames.length; i++) {
1✔
1086
                frames[i] = getSubframeContents(names[i]).getScriptableObject();
1✔
1087
            }
1088
            return frames;
1✔
1089
        }
1090

1091
        /**
1092
         * Load.
1093
         *
1094
         * @throws SAXException
1095
         *             the SAX exception
1096
         */
1097
        public void load() throws SAXException {
1098
            if (isHTML() && isWithParse()) {
1!
1099
                getReceivedPage().getForms(); // TODO be more explicit here - don't care about forms, after all
1✔
1100
                doEventScript(getReceivedPage().getOnLoadEvent());
1✔
1101
            }
1102
        }
1✔
1103

1104
        /**
1105
         * Open.
1106
         *
1107
         * @param urlString
1108
         *            the url string
1109
         * @param name
1110
         *            the name
1111
         * @param features
1112
         *            the features
1113
         * @param replace
1114
         *            the replace
1115
         *
1116
         * @return the scriptable
1117
         *
1118
         * @throws IOException
1119
         *             Signals that an I/O exception has occurred.
1120
         * @throws SAXException
1121
         *             the SAX exception
1122
         */
1123
        public Scriptable open(String urlString, String name, String features, boolean replace)
1124
                throws IOException, SAXException {
1125
            WebResponse response = (WebResponse) openNewWindow(name, urlString);
1✔
1126
            return response == null ? null : response.getScriptableObject();
1✔
1127
        }
1128

1129
        /**
1130
         * Close window.
1131
         */
1132
        public void closeWindow() {
1133
            close();
1✔
1134
        }
1✔
1135

1136
        /**
1137
         * Returns the value of the named property. Will return null if the property does not exist.
1138
         **/
1139
        @Override
1140
        public Object get(String propertyName) {
1141
            if (propertyName.equals("name")) {
1✔
1142
                return getName();
1✔
1143
            }
1144
            if (propertyName.equalsIgnoreCase("top")) {
1✔
1145
                return _window.getFrameContents(WebRequest.TOP_FRAME).getScriptableObject();
1✔
1146
            }
1147
            if (propertyName.equalsIgnoreCase("parent")) {
1✔
1148
                return _window.getParentFrameContents(_frame).getScriptableObject();
1✔
1149
            }
1150
            if (propertyName.equalsIgnoreCase("opener")) {
1✔
1151
                return getFrameName().equals(WebRequest.TOP_FRAME) ? getScriptable(_window.getOpener()) : null;
1!
1152
            }
1153
            if (propertyName.equalsIgnoreCase("closed")) {
1✔
1154
                return getFrameName().equals(WebRequest.TOP_FRAME) && _window.isClosed() ? Boolean.TRUE : Boolean.FALSE;
1!
1155
            }
1156
            try {
1157
                return getSubframeContents(propertyName).getScriptableObject();
1✔
1158
            } catch (NoSuchFrameException e) {
1✔
1159
                return super.get(propertyName);
1✔
1160
            }
1161
        }
1162

1163
        @Override
1164
        public String getName() {
1165
            String windowName = getFrameName().equals(WebRequest.TOP_FRAME) ? _window.getName() : getFrameName();
1✔
1166
            return windowName.startsWith(WebWindow.NO_NAME) ? "" : windowName;
1✔
1167
        }
1168

1169
        /**
1170
         * Gets the scriptable.
1171
         *
1172
         * @param opener
1173
         *            the opener
1174
         *
1175
         * @return the scriptable
1176
         */
1177
        private Scriptable getScriptable(WebResponse opener) {
1178
            return opener == null ? null : opener.getScriptableObject();
1✔
1179
        }
1180

1181
        /**
1182
         * Sets the value of the named property. Will throw a runtime exception if the property does not exist or cannot
1183
         * accept the specified value.
1184
         **/
1185
        @Override
1186
        public void set(String propertyName, Object value) {
1187
            if (propertyName.equals("name")) {
1!
1188
                if (value == null) {
1!
1189
                    value = "";
×
1190
                }
1191
                if (getFrameName().equals(WebRequest.TOP_FRAME)) {
1!
1192
                    _window.setName(value.toString());
1✔
1193
                }
1194
            } else {
1195
                super.set(propertyName, value);
×
1196
            }
1197
        }
1✔
1198

1199
        /**
1200
         * Sets the location.
1201
         *
1202
         * @param relativeURL
1203
         *            the new location
1204
         *
1205
         * @throws IOException
1206
         *             Signals that an I/O exception has occurred.
1207
         * @throws SAXException
1208
         *             the SAX exception
1209
         */
1210
        public void setLocation(String relativeURL) throws IOException, SAXException {
1211
            getWindow().getResponse(new GetMethodWebRequest(_pageURL, relativeURL, _frame.getName()));
1✔
1212
        }
1✔
1213

1214
        /**
1215
         * Gets the url.
1216
         *
1217
         * @return the url
1218
         */
1219
        public URL getURL() {
1220
            return WebResponse.this._pageURL;
1✔
1221
        }
1222
    }
1223

1224
    // ---------------------------------------- Object methods --------------------------------------------
1225

1226
    @Override
1227
    public abstract String toString();
1228

1229
    // ----------------------------------------- protected members -----------------------------------------------
1230

1231
    /**
1232
     * Constructs a response object. see [ 1159858 ] patch for RFE 1159844 (parsing intercepted pages)
1233
     *
1234
     * @param client
1235
     *            the client
1236
     * @param frame
1237
     *            the frame to hold the response
1238
     * @param url
1239
     *            the url from which the response was received
1240
     */
1241
    protected WebResponse(WebClient client, FrameSelector frame, URL url) {
1✔
1242
        _client = client;
1✔
1243
        _baseURL = _pageURL = url;
1✔
1244
        _baseTarget = frame.getName();
1✔
1245
        _frame = frame;
1✔
1246
        // intialize window for interception as described in
1247
        // https://sourceforge.net/tracker/index.php?func=detail&aid=1159844&group_id=6550&atid=356550
1248
        if (client != null) {
1✔
1249
            _window = client.getMainWindow();
1✔
1250
        }
1251
    }
1✔
1252

1253
    /**
1254
     * Constructs a response object.
1255
     *
1256
     * @param client
1257
     *            the client
1258
     * @param frame
1259
     *            the frame to hold the response
1260
     * @param url
1261
     *            the url from which the response was received
1262
     * @param text
1263
     *            the text
1264
     */
1265
    protected WebResponse(WebClient client, FrameSelector frame, URL url, String text) {
1266
        this(client, frame, url);
1✔
1267
        _responseText = text;
1✔
1268
    }
1✔
1269

1270
    /**
1271
     * Define raw input stream.
1272
     *
1273
     * @param inputStream
1274
     *            the input stream
1275
     *
1276
     * @throws IOException
1277
     *             Signals that an I/O exception has occurred.
1278
     */
1279
    protected final void defineRawInputStream(InputStream inputStream) throws IOException {
1280
        if (_inputStream != null || _responseText != null) {
1!
1281
            throw new IllegalStateException("Must be called before response text is defined.");
×
1282
        }
1283

1284
        // please note bug report [ 1119205 ] EOFExceptions while using a Proxy
1285
        // and patch proposal below
1286
        // by Ralf Bust
1287
        /*
1288
         * original 1.6.2 code if (encodedUsingGZIP()) { byte[] compressedData = readFromStream( inputStream,
1289
         * getContentLength() ); _inputStream = new GZIPInputStream( new ByteArrayInputStream( compressedData ) ); }
1290
         * else { _inputStream = inputStream; }
1291
         */
1292

1293
        if (encodedUsingGZIP()) {
1✔
1294
            try {
1295
                _inputStream = new GZIPInputStream(inputStream);
1✔
1296
            } catch (EOFException eof) {
×
1297
                _inputStream = inputStream;
×
1298
            }
1✔
1299
        } else {
1300
            _inputStream = inputStream;
1✔
1301
        }
1302
    }
1✔
1303

1304
    /**
1305
     * Encoded using GZIP.
1306
     *
1307
     * @return true, if successful
1308
     */
1309
    private boolean encodedUsingGZIP() {
1310
        String encoding = getHeaderField("Content-Encoding");
1✔
1311
        return encoding != null && encoding.indexOf("gzip") >= 0;
1!
1312
    }
1313

1314
    /**
1315
     * Overwrites the current value (if any) of the content type header.
1316
     *
1317
     * @param value
1318
     *            the new content type header
1319
     */
1320
    protected void setContentTypeHeader(String value) {
1321
        _contentHeader = value;
1✔
1322
    }
1✔
1323

1324
    // ------------------------------------------ package members ------------------------------------------------
1325

1326
    /** The Constant BLANK_HTML. */
1327
    static final String BLANK_HTML = "";
1328

1329
    /**
1330
     * Creates the blank response.
1331
     *
1332
     * @return the web response
1333
     */
1334
    static WebResponse createBlankResponse() {
1335
        return new DefaultWebResponse(BLANK_HTML);
1✔
1336
    }
1337

1338
    /**
1339
     * Gets the window.
1340
     *
1341
     * @return the window
1342
     */
1343
    WebWindow getWindow() {
1344
        return _window;
1✔
1345
    }
1346

1347
    /**
1348
     * Sets the window.
1349
     *
1350
     * @param window
1351
     *            the new window
1352
     */
1353
    void setWindow(WebWindow window) {
1354
        _window = window;
1✔
1355
    }
1✔
1356

1357
    /**
1358
     * replace the given text
1359
     *
1360
     * @param text
1361
     *            - the text to replace
1362
     * @param contentType
1363
     *            - the contenttype
1364
     */
1365
    @Override
1366
    public boolean replaceText(String text, String contentType) {
1367
        if (_parsingPage) {
1✔
1368
            return false;
1✔
1369
        }
1370
        _responseText = text;
1✔
1371
        _inputStream = null;
1✔
1372
        _page = null;
1✔
1373
        _contentType = contentType;
1✔
1374
        _baseURL = null;
1✔
1375
        _baseTarget = _frame.getName();
1✔
1376
        _refreshHeader = null;
1✔
1377

1378
        try {
1379
            readTags(text.getBytes(StandardCharsets.UTF_8));
1✔
1380
        } catch (MalformedURLException e) {
×
1381
            throw new RuntimeException("Failure while attempting to reparse text: " + e);
×
1382
        }
1✔
1383
        return true;
1✔
1384
    }
1385

1386
    /**
1387
     * Returns the frames found in the page in the order in which they appear.
1388
     *
1389
     * @return the frame requests
1390
     *
1391
     * @throws SAXException
1392
     *             the SAX exception
1393
     */
1394
    WebRequest[] getFrameRequests() throws SAXException {
1395
        WebFrame[] frames = getFrames();
1✔
1396
        List<WebRequest> requests = new ArrayList<>();
1✔
1397
        for (WebFrame frame : frames) {
1✔
1398
            if (frame.hasInitialRequest()) {
1✔
1399
                requests.add(frame.getInitialRequest());
1✔
1400
            }
1401
        }
1402

1403
        return requests.toArray(new WebRequest[requests.size()]);
1✔
1404
    }
1405

1406
    // --------------------------------- private members --------------------------------------
1407

1408
    /** The window. */
1409
    private WebWindow _window;
1410

1411
    /** The page. */
1412
    private HTMLPage _page;
1413

1414
    /** The content header. */
1415
    private String _contentHeader;
1416

1417
    /** The content length. */
1418
    private int _contentLength = UNINITIALIZED_INT;
1✔
1419

1420
    /** The content type. */
1421
    private String _contentType;
1422

1423
    /** The character set. */
1424
    private String _characterSet;
1425

1426
    /** The refresh request. */
1427
    private WebRequest _refreshRequest;
1428

1429
    /** The refresh delay. */
1430
    private int _refreshDelay = -1; // initialized to invalid value
1✔
1431

1432
    /** the response as a String. */
1433
    private String _responseText;
1434

1435
    /** the response as a byte array. */
1436
    private byte[] _bytes;
1437

1438
    /** The input stream. */
1439
    private InputStream _inputStream;
1440

1441
    /** The page URL. */
1442
    private final URL _pageURL;
1443

1444
    /** The client. */
1445
    private final WebClient _client;
1446

1447
    /**
1448
     * getter for the WebClient.
1449
     *
1450
     * @return the web client for this WebResponse (if any)
1451
     */
1452
    public WebClient getClient() {
1453
        return _client;
×
1454
    }
1455

1456
    /** The scripting handler. */
1457
    private ScriptingHandler _scriptingHandler;
1458

1459
    /**
1460
     * Load response text.
1461
     *
1462
     * @throws IOException
1463
     *             Signals that an I/O exception has occurred.
1464
     */
1465
    protected void loadResponseText() throws IOException {
1466
        if (_responseText != null) {
1!
1467
            throw new IllegalStateException("May only invoke loadResponseText once");
×
1468
        }
1469
        _responseText = "";
1✔
1470

1471
        try (InputStream inputStream = getInputStream()) {
1✔
1472
            final int contentLength = this.encodedUsingGZIP() ? -1 : getContentLength();
1✔
1473
            int bytesRemaining = contentLength < 0 ? Integer.MAX_VALUE : contentLength;
1✔
1474
            _bytes = readFromStream(inputStream, bytesRemaining);
1✔
1475

1476
            readTags(_bytes);
1✔
1477
            _responseText = new String(_bytes, Charset.forName(getCharacterSet()));
1✔
1478
            _inputStream = new ByteArrayInputStream(_bytes);
1✔
1479

1480
            if (HttpUnitOptions.isCheckContentLength() && contentLength >= 0 && _bytes.length != contentLength) {
1!
1481
                throw new IOException(
×
1482
                        "Truncated message. Expected length: " + contentLength + ", Actual length: " + _bytes.length);
1483
            }
1484
        }
1485
    }
1✔
1486

1487
    /**
1488
     * Read from stream.
1489
     *
1490
     * @param inputStream
1491
     *            the input stream
1492
     * @param maxBytes
1493
     *            the max bytes
1494
     *
1495
     * @return the byte[]
1496
     *
1497
     * @throws IOException
1498
     *             Signals that an I/O exception has occurred.
1499
     */
1500
    private byte[] readFromStream(InputStream inputStream, int maxBytes) throws IOException {
1501
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1✔
1502
        byte[] buffer = new byte[8 * 1024];
1✔
1503
        int count = 0;
1✔
1504
        if (maxBytes > 0) {
1✔
1505
            do {
1506
                outputStream.write(buffer, 0, count);
1✔
1507
                maxBytes -= count;
1✔
1508
                if (maxBytes <= 0) {
1✔
1509
                    break;
1✔
1510
                }
1511
                count = inputStream.read(buffer, 0, Math.min(maxBytes, buffer.length));
1✔
1512
            } while (count != -1);
1✔
1513
        } else {
1514
            do {
1515
                outputStream.write(buffer, 0, count);
1✔
1516
                int available = getAvailableBytes(inputStream);
1✔
1517
                count = available == 0 ? -1 : inputStream.read(buffer, 0, buffer.length);
1!
1518
            } while (count != -1);
1!
1519
        }
1520

1521
        return outputStream.toByteArray();
1✔
1522
    }
1523

1524
    /**
1525
     * Gets the available bytes.
1526
     *
1527
     * @param inputStream
1528
     *            the input stream
1529
     *
1530
     * @return the available bytes
1531
     *
1532
     * @throws IOException
1533
     *             Signals that an I/O exception has occurred.
1534
     */
1535
    private int getAvailableBytes(InputStream inputStream) throws IOException {
1536
        int timeLeft = UNKNOWN_LENGTH_TIMEOUT;
1✔
1537
        int available;
1538
        do {
1539
            timeLeft -= UNKNOWN_LENGTH_RETRY_INTERVAL;
1✔
1540
            try {
1541
                Thread.sleep(UNKNOWN_LENGTH_RETRY_INTERVAL);
1✔
1542
            } catch (InterruptedException e) {
×
1543
                Thread.interrupted();
×
1544
                /* do nothing */
1545
            }
1✔
1546
            available = inputStream.available();
1✔
1547
        } while (available == 0 && timeLeft > 0);
1!
1548
        return available;
1✔
1549
    }
1550

1551
    /**
1552
     * read the tags from the given message.
1553
     *
1554
     * @param rawMessage
1555
     *            the raw message
1556
     *
1557
     * @throws MalformedURLException
1558
     *             the malformed URL exception
1559
     */
1560
    private void readTags(byte[] rawMessage) throws MalformedURLException {
1561
        ByteTagParser parser = new ByteTagParser(rawMessage);
1✔
1562
        ByteTag tag = parser.getNextTag();
1✔
1563
        while (tag != null) {
1✔
1564
            if (tag.getName().equalsIgnoreCase("meta")) {
1✔
1565
                processMetaTag(tag);
1✔
1566
            }
1567
            if (tag.getName().equalsIgnoreCase("base")) {
1✔
1568
                processBaseTag(tag);
1✔
1569
            }
1570
            // loop over a noscript region
1571
            if (tag.getName().equalsIgnoreCase("noscript") && HttpUnitOptions.isScriptingEnabled()) {
1✔
1572
                do {
1573
                    tag = parser.getNextTag();
1✔
1574
                } while (!tag.getName().equalsIgnoreCase("/noscript"));
1✔
1575
            }
1576
            tag = parser.getNextTag();
1✔
1577
        }
1578
    }
1✔
1579

1580
    /**
1581
     * Process base tag.
1582
     *
1583
     * @param tag
1584
     *            the tag
1585
     *
1586
     * @throws MalformedURLException
1587
     *             the malformed URL exception
1588
     */
1589
    private void processBaseTag(ByteTag tag) throws MalformedURLException {
1590
        if (tag.getAttribute("href") != null) {
1✔
1591
            _baseURL = new URL(getURL(), tag.getAttribute("href"));
1✔
1592
        }
1593
        if (tag.getAttribute("target") != null) {
1✔
1594
            _baseTarget = tag.getAttribute("target");
1✔
1595
        }
1596
    }
1✔
1597

1598
    /**
1599
     * process MetaTags based on the tag.
1600
     *
1601
     * @param tag
1602
     *            the tag
1603
     */
1604
    private void processMetaTag(ByteTag tag) {
1605
        if (isHttpEquivMetaTag(tag, "content-type")) {
1✔
1606
            inferContentType(tag.getAttribute("content"));
1✔
1607
        } else if (isHttpEquivMetaTag(tag, "refresh")) {
1✔
1608
            inferRefreshHeader(tag.getAttribute("content"));
1✔
1609
        }
1610
    }
1✔
1611

1612
    /**
1613
     * check whether the given tag is a http equiv meta tag.
1614
     *
1615
     * @param tag
1616
     *            the tag
1617
     * @param headerName
1618
     *            the header name
1619
     *
1620
     * @return true, if is http equiv meta tag
1621
     */
1622
    private boolean isHttpEquivMetaTag(ByteTag tag, String headerName) {
1623
        String equiv1 = tag.getAttribute("http_equiv");
1✔
1624
        String equiv2 = tag.getAttribute("http-equiv");
1✔
1625
        return headerName.equalsIgnoreCase(equiv1) || headerName.equalsIgnoreCase(equiv2);
1✔
1626
    }
1627

1628
    /**
1629
     * infer the refresh Header.
1630
     *
1631
     * @param refreshHeader
1632
     *            the refresh header
1633
     */
1634
    private void inferRefreshHeader(String refreshHeader) {
1635
        String originalHeader = getHeaderField("Refresh");
1✔
1636
        // System.err.println("original='"+originalHeader+"'\nrefreshHeader='"+refreshHeader+"'");
1637
        if (originalHeader == null) {
1!
1638
            _refreshHeader = refreshHeader;
1✔
1639
        }
1640
    }
1✔
1641

1642
    /**
1643
     * read the Refresh Request.
1644
     */
1645
    private void readRefreshRequest() {
1646
        if (_refreshDelay >= 0) {
1✔
1647
            return;
1✔
1648
        }
1649
        _refreshDelay = 0;
1✔
1650
        String refreshHeader = _refreshHeader != null ? _refreshHeader : getHeaderField("Refresh");
1✔
1651
        if (refreshHeader == null) {
1✔
1652
            return;
1✔
1653
        }
1654

1655
        int semicolonIndex = refreshHeader.indexOf(';');
1✔
1656
        if (semicolonIndex < 0) {
1✔
1657
            interpretRefreshHeaderElement(refreshHeader, refreshHeader);
1✔
1658
        } else {
1659
            interpretRefreshHeaderElement(refreshHeader.substring(0, semicolonIndex), refreshHeader);
1✔
1660
            interpretRefreshHeaderElement(refreshHeader.substring(semicolonIndex + 1), refreshHeader);
1✔
1661
        }
1662
        if (_refreshRequest == null) {
1✔
1663
            _refreshRequest = new GetMethodWebRequest(_pageURL, _pageURL.toString(), _frame.getName());
1✔
1664
        }
1665
    }
1✔
1666

1667
    /**
1668
     * Interpret refresh header element.
1669
     *
1670
     * @param token
1671
     *            the token
1672
     * @param refreshHeader
1673
     *            the refresh header
1674
     */
1675
    private void interpretRefreshHeaderElement(String token, String refreshHeader) {
1676
        if (token.isEmpty()) {
1!
1677
            return;
×
1678
        }
1679
        try {
1680
            if (Character.isDigit(token.charAt(0))) {
1✔
1681
                _refreshDelay = Integer.parseInt(token);
1✔
1682
            } else {
1683
                _refreshRequest = new GetMethodWebRequest(_pageURL, getRefreshURL(token), _frame.getName());
1✔
1684
            }
1685
        } catch (NumberFormatException e) {
×
1686
            System.out.println("Unable to interpret refresh tag: \"" + refreshHeader + '"');
×
1687
        }
1✔
1688
    }
1✔
1689

1690
    /**
1691
     * Gets the refresh URL.
1692
     *
1693
     * @param text
1694
     *            the text
1695
     *
1696
     * @return the refresh URL
1697
     */
1698
    private String getRefreshURL(String text) {
1699
        text = text.trim();
1✔
1700
        if (!text.toUpperCase().startsWith("URL")) {
1✔
1701
            return HttpUnitUtils.stripQuotes(text);
1✔
1702
        }
1703
        int splitIndex = text.indexOf('=');
1✔
1704
        String value = text.substring(splitIndex + 1).trim();
1✔
1705
        return HttpUnitUtils.replaceEntities(HttpUnitUtils.stripQuotes(value));
1✔
1706
    }
1707

1708
    /**
1709
     * Infer content type.
1710
     *
1711
     * @param contentTypeHeader
1712
     *            the content type header
1713
     */
1714
    private void inferContentType(String contentTypeHeader) {
1715
        String originalHeader = getHeaderField("Content-type");
1✔
1716
        if (originalHeader == null || originalHeader.indexOf("charset") < 0) {
1!
1717
            setContentTypeHeader(contentTypeHeader);
1✔
1718
        }
1719
    }
1✔
1720

1721
    /**
1722
     * Gets the cookie jar.
1723
     *
1724
     * @return the cookie jar
1725
     */
1726
    CookieJar getCookieJar() {
1727
        if (_cookies == null) {
1✔
1728
            _cookies = new CookieJar(this);
1✔
1729
        }
1730
        return _cookies;
1✔
1731
    }
1732

1733
    /** The cookies. */
1734
    private CookieJar _cookies;
1735

1736
    /**
1737
     * Read content type header.
1738
     */
1739
    private void readContentTypeHeader() {
1740
        String contentHeader = _contentHeader != null ? _contentHeader : getHeaderField("Content-type");
1✔
1741
        if (contentHeader == null) {
1!
1742
            _contentType = HttpUnitOptions.getDefaultContentType();
×
1743
            setCharacterSet(HttpUnitOptions.getDefaultCharacterSet());
×
1744
            _contentHeader = _contentType + ";charset=" + _characterSet;
×
1745
        } else {
1746
            String[] parts = HttpUnitUtils.parseContentTypeHeader(contentHeader);
1✔
1747
            if (null != _client && null != _client.getClientProperties().getOverrideContentType()) {
1✔
1748
                _contentType = _client.getClientProperties().getOverrideContentType();
1✔
1749
            } else {
1750
                _contentType = parts[0];
1✔
1751
            }
1752
            if (parts[1] != null) {
1✔
1753
                setCharacterSet(parts[1]);
1✔
1754
            }
1755
        }
1756
    }
1✔
1757

1758
    /**
1759
     * Gets the frames.
1760
     *
1761
     * @return the frames
1762
     *
1763
     * @throws SAXException
1764
     *             the SAX exception
1765
     */
1766
    private WebFrame[] getFrames() throws SAXException {
1767
        if (isWithParse()) {
1✔
1768
            return getReceivedPage().getFrames();
1✔
1769
        }
1770
        return new WebFrame[0];
1✔
1771
    }
1772

1773
    /**
1774
     * get the received Page.
1775
     *
1776
     * @return the received page
1777
     *
1778
     * @throws SAXException
1779
     *             the SAX exception
1780
     */
1781
    HTMLPage getReceivedPage() throws SAXException {
1782
        if (_page == null) {
1✔
1783
            try {
1784
                _parsingPage = true;
1✔
1785
                if (HttpUnitOptions.isCheckHtmlContentType() && !isHTML()) {
1✔
1786
                    throw new NotHTMLException(getContentType());
1✔
1787
                }
1788
                _page = new HTMLPage(this, _frame, _baseURL, _baseTarget, getCharacterSet());
1✔
1789
                if (_withParse) {
1!
1790
                    _page.parse(getText(), _pageURL);
1✔
1791
                    if (_page == null) {
1!
1792
                        throw new IllegalStateException("replaceText called in the middle of getReceivedPage()");
×
1793
                    }
1794
                    ((HTMLDocumentImpl) _page.getRootNode()).getWindow().setProxy(this);
1✔
1795
                }
1796
            } catch (IOException e) {
×
1797
                HttpUnitUtils.handleException(e);
×
1798
                throw new RuntimeException(e.toString());
×
1799
            } finally {
1800
                _parsingPage = false;
1✔
1801
            }
1802
        }
1803
        return _page;
1✔
1804
    }
1805

1806
    /** The default encoding. */
1807
    private static String _defaultEncoding;
1808

1809
    /** The Constant DEFAULT_ENCODING_CANDIDATES. */
1810
    private static final String[] DEFAULT_ENCODING_CANDIDATES = { StandardCharsets.ISO_8859_1.name(),
1✔
1811
            StandardCharsets.US_ASCII.name() };
1✔
1812

1813
    /**
1814
     * Gets the default encoding.
1815
     *
1816
     * @return the default encoding
1817
     */
1818
    static String getDefaultEncoding() {
1819
        if (_defaultEncoding == null) {
1✔
1820
            for (String element : DEFAULT_ENCODING_CANDIDATES) {
1!
1821
                if (isSupportedCharacterSet(element)) {
1!
1822
                    return _defaultEncoding = element;
1✔
1823
                }
1824
            }
1825
            _defaultEncoding = Charset.defaultCharset().displayName();
×
1826
        }
1827
        return _defaultEncoding;
1✔
1828
    }
1829

1830
    /**
1831
     * Sets the character set.
1832
     *
1833
     * @param characterSet
1834
     *            the new character set
1835
     */
1836
    private void setCharacterSet(String characterSet) {
1837
        if (characterSet == null) {
1✔
1838
            return;
1✔
1839
        }
1840

1841
        _characterSet = isSupportedCharacterSet(characterSet) ? characterSet : getDefaultEncoding();
1✔
1842
    }
1✔
1843

1844
    /**
1845
     * Checks if is supported character set.
1846
     *
1847
     * @param characterSet
1848
     *            the character set
1849
     *
1850
     * @return true, if is supported character set
1851
     */
1852
    private static boolean isSupportedCharacterSet(String characterSet) {
1853
        try {
1854
            return "abcd".getBytes(Charset.forName(characterSet)).length > 0;
1!
1855
        } catch (UnsupportedCharsetException e) {
1✔
1856
            return false;
1✔
1857
        }
1858
    }
1859

1860
    /**
1861
     * Sets the cookie.
1862
     *
1863
     * @param name
1864
     *            the name
1865
     * @param value
1866
     *            the value
1867
     */
1868
    void setCookie(String name, String value) {
1869
        _client.putCookie(name, value);
1✔
1870
    }
1✔
1871

1872
    /**
1873
     * Gets the cookie header.
1874
     *
1875
     * @return the cookie header
1876
     */
1877
    String getCookieHeader() {
1878
        return _client.getCookieJar().getCookieHeaderField(getURL());
1✔
1879
    }
1880

1881
    /**
1882
     * Gets the referer.
1883
     *
1884
     * @return the referer
1885
     */
1886
    String getReferer() {
1887
        return null;
1✔
1888
    }
1889

1890
    // =======================================================================================
1891

1892
    /**
1893
     * The Class ByteTag.
1894
     */
1895
    static class ByteTag {
1896

1897
        /**
1898
         * Instantiates a new byte tag.
1899
         *
1900
         * @param buffer
1901
         *            the buffer
1902
         * @param start
1903
         *            the start
1904
         * @param length
1905
         *            the length
1906
         */
1907
        ByteTag(byte[] buffer, int start, int length) {
1✔
1908
            _buffer = new String(buffer, start, length, Charset.forName(WebResponse.getDefaultEncoding()))
1✔
1909
                    .toCharArray();
1✔
1910
            _name = nextToken();
1✔
1911

1912
            String attribute = "";
1✔
1913
            String token = nextToken();
1✔
1914
            while (!token.isEmpty()) {
1✔
1915
                if (token.equals("=") && !attribute.isEmpty()) {
1✔
1916
                    getAttributes().put(attribute.toLowerCase(Locale.ENGLISH), nextToken());
1✔
1917
                    attribute = "";
1✔
1918
                } else {
1919
                    if (!attribute.isEmpty()) {
1✔
1920
                        getAttributes().put(attribute.toLowerCase(Locale.ENGLISH), "");
1✔
1921
                    }
1922
                    attribute = token;
1✔
1923
                }
1924
                token = nextToken();
1✔
1925
            }
1926
        }
1✔
1927

1928
        /**
1929
         * Gets the name.
1930
         *
1931
         * @return the name
1932
         */
1933
        public String getName() {
1934
            return _name;
1✔
1935
        }
1936

1937
        /**
1938
         * Gets the attribute.
1939
         *
1940
         * @param attributeName
1941
         *            the attribute name
1942
         *
1943
         * @return the attribute
1944
         */
1945
        public String getAttribute(String attributeName) {
1946
            return (String) getAttributes().get(attributeName);
1✔
1947
        }
1948

1949
        @Override
1950
        public String toString() {
1951
            return "ByteTag[ name=" + _name + ";attributes = " + _attributes + ']';
×
1952
        }
1953

1954
        /**
1955
         * Gets the attributes.
1956
         *
1957
         * @return the attributes
1958
         */
1959
        private Hashtable getAttributes() {
1960
            if (_attributes == null) {
1✔
1961
                _attributes = new Hashtable<>();
1✔
1962
            }
1963
            return _attributes;
1✔
1964
        }
1965

1966
        /** The name. */
1967
        private String _name = "";
1✔
1968

1969
        /** The attributes. */
1970
        private Hashtable _attributes;
1971

1972
        /** The buffer. */
1973
        private char[] _buffer;
1974

1975
        /** The end. */
1976
        private int _end = -1;
1✔
1977

1978
        /**
1979
         * Next token.
1980
         *
1981
         * @return the string
1982
         */
1983
        private String nextToken() {
1984
            int start = _end + 1;
1✔
1985
            while (start < _buffer.length && Character.isWhitespace(_buffer[start])) {
1✔
1986
                start++;
1✔
1987
            }
1988
            if (start >= _buffer.length) {
1✔
1989
                return "";
1✔
1990
            }
1991
            if (_buffer[start] == '"') {
1✔
1992
                for (_end = start + 1; _end < _buffer.length && _buffer[_end] != '"'; _end++) {
1✔
1993

1994
                }
1995
                return new String(_buffer, start + 1, _end - start - 1);
1✔
1996
            }
1997
            if (_buffer[start] == '\'') {
1✔
1998
                for (_end = start + 1; _end < _buffer.length && _buffer[_end] != '\''; _end++) {
1✔
1999

2000
                }
2001
                return new String(_buffer, start + 1, _end - start - 1);
1✔
2002
            }
2003
            if (_buffer[start] == '=') {
1✔
2004
                _end = start;
1✔
2005
                return "=";
1✔
2006
            }
2007
            for (_end = start + 1; _end < _buffer.length && _buffer[_end] != '='
1✔
2008
                    && !Character.isWhitespace(_buffer[_end]); _end++) {
1✔
2009

2010
            }
2011
            return new String(_buffer, start, _end-- - start);
1✔
2012
        }
2013
    }
2014

2015
    // =======================================================================================
2016

2017
    /**
2018
     * The Class ByteTagParser.
2019
     */
2020
    static class ByteTagParser {
2021

2022
        /**
2023
         * Instantiates a new byte tag parser.
2024
         *
2025
         * @param buffer
2026
         *            the buffer
2027
         */
2028
        ByteTagParser(byte[] buffer) {
1✔
2029
            _buffer = buffer;
1✔
2030
        }
1✔
2031

2032
        /**
2033
         * Gets the next tag.
2034
         *
2035
         * @return the next tag
2036
         */
2037
        ByteTag getNextTag() {
2038
            ByteTag byteTag = null;
1✔
2039
            do {
2040
                int _start = _end + 1;
1✔
2041
                while (_start < _buffer.length && _buffer[_start] != '<') {
1✔
2042
                    _start++;
1✔
2043
                }
2044
                // proposed patch for bug report
2045
                // [ 1376739 ] iframe tag not recognized if Javascript code contains '<'
2046
                // by Nathan Jakubiak
2047
                // uncommented since it doesn't seem to fix the test in WebFrameTest.java
2048
                // if (_scriptDepth > 0 && _start+1 < _buffer.length &&
2049
                // _buffer[ _start+1 ] != '/') {
2050
                // _end = _start+1;
2051
                // continue;
2052
                // }
2053
                for (_end = _start + 1; _end < _buffer.length && _buffer[_end] != '>'; _end++) {
1✔
2054

2055
                }
2056
                if (_end >= _buffer.length || _end < _start) {
1!
2057
                    return null;
1✔
2058
                }
2059
                byteTag = new ByteTag(_buffer, _start + 1, _end - _start - 1);
1✔
2060
                if (byteTag.getName().equalsIgnoreCase("script")) {
1✔
2061
                    _scriptDepth++;
1✔
2062
                    return byteTag;
1✔
2063
                }
2064
                if (byteTag.getName().equalsIgnoreCase("/script")) {
1✔
2065
                    _scriptDepth--;
1✔
2066
                }
2067
            } while (_scriptDepth > 0);
1✔
2068
            return byteTag;
1✔
2069
        }
2070

2071
        /** The script depth. */
2072
        private int _scriptDepth = 0;
1✔
2073

2074
        /** The end. */
2075
        private int _end = -1;
1✔
2076

2077
        /** The buffer. */
2078
        private byte[] _buffer;
2079
    }
2080

2081
    /**
2082
     * allow access to the valid content Types.
2083
     *
2084
     * @return the validContentTypes
2085
     */
2086
    public static String[] getValidContentTypes() {
2087
        return validContentTypes;
×
2088
    }
2089

2090
    /**
2091
     * allow modification of the valid content Types use with care.
2092
     *
2093
     * @param validContentTypes
2094
     *            the validContentTypes to set
2095
     */
2096
    protected static void setValidContentTypes(String[] validContentTypes) {
2097
        WebResponse.validContentTypes = validContentTypes;
×
2098
    }
×
2099

2100
}
2101

2102
// =======================================================================================
2103

2104
class DefaultWebResponse extends WebResponse {
2105

2106
    DefaultWebResponse(String text) {
2107
        this(null, null, text);
1✔
2108
    }
1✔
2109

2110
    DefaultWebResponse(WebClient client, URL url, String text) {
2111
        this(client, FrameSelector.TOP_FRAME, url, text);
1✔
2112
    }
1✔
2113

2114
    DefaultWebResponse(WebClient client, FrameSelector frame, URL url, String text) {
2115
        super(client, frame, url, text);
1✔
2116
    }
1✔
2117

2118
    /**
2119
     * Returns the response code associated with this response.
2120
     **/
2121
    @Override
2122
    public int getResponseCode() {
2123
        return HttpURLConnection.HTTP_OK;
1✔
2124
    }
2125

2126
    /**
2127
     * Returns the response message associated with this response.
2128
     **/
2129
    @Override
2130
    public String getResponseMessage() {
2131
        return "OK";
×
2132
    }
2133

2134
    @Override
2135
    public String[] getHeaderFieldNames() {
2136
        return new String[] { "Content-type" };
×
2137
    }
2138

2139
    /**
2140
     * Returns the value for the specified header field. If no such field is defined, will return null.
2141
     **/
2142
    @Override
2143
    public String getHeaderField(String fieldName) {
2144
        if (fieldName.equalsIgnoreCase("Content-type")) {
1✔
2145
            return "text/html; charset=us-ascii";
1✔
2146
        }
2147
        return null;
1✔
2148
    }
2149

2150
    @Override
2151
    public String[] getHeaderFields(String fieldName) {
2152
        String value = getHeaderField(fieldName);
1✔
2153
        return value == null ? new String[0] : new String[] { value };
1!
2154
    }
2155

2156
    @Override
2157
    public String toString() {
2158
        try {
2159
            return "DefaultWebResponse [" + getText() + "]";
×
2160
        } catch (IOException e) { // should never happen
×
2161
            return "DefaultWebResponse [???]";
×
2162
        }
2163
    }
2164
}
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