• 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

89.53
/src/main/java/com/meterware/httpunit/WebWindow.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.scripting.ScriptingHandler;
23

24
import java.io.IOException;
25
import java.net.HttpURLConnection;
26
import java.net.MalformedURLException;
27
import java.net.URL;
28
import java.util.Hashtable;
29
import java.util.List;
30
import java.util.Map;
31

32
import org.xml.sax.SAXException;
33

34
/**
35
 * A window managed by a {@link com.meterware.httpunit.WebClient WebClient}.
36
 **/
37
public class WebWindow {
38

39
    /** The client which created this window. **/
40
    private WebClient _client;
41

42
    /** A map of frame names to current contents. **/
43
    private FrameHolder _frameContents;
44

45
    /** The name of the window, set via JavaScript. **/
46
    private String _name = "";
1✔
47

48
    /** The web response containing the reference that opened this window *. */
49
    private WebResponse _opener;
50

51
    /** True if this window has been closed. **/
52
    private boolean _closed;
53

54
    /** The Constant NO_NAME. */
55
    static final String NO_NAME = "$$HttpUnit_Window$$_";
56

57
    /** The urls that have been encountered as redirect locations in the course of a single client-initiated request. */
58
    private final Map _redirects;
59

60
    /** True if seen initial request. */
61
    private boolean _isInitialRequest = true;
1✔
62

63
    /**
64
     * Cache the initial client request to ensure that the _redirects structure gets reset.
65
     */
66
    private WebRequest _initialRequest;
67

68
    /**
69
     * Returns the web client associated with this window.
70
     *
71
     * @return the client
72
     */
73
    public WebClient getClient() {
74
        return _client;
1✔
75
    }
76

77
    /**
78
     * Returns true if this window has been closed.
79
     *
80
     * @return true, if is closed
81
     */
82
    public boolean isClosed() {
83
        return _closed;
1✔
84
    }
85

86
    /**
87
     * Closes this window.
88
     */
89
    public void close() {
90
        if (!_closed) {
1!
91
            _client.close(this);
1✔
92
        }
93
        _closed = true;
1✔
94
    }
1✔
95

96
    /**
97
     * Returns the name of this window. Windows created through normal HTML or browser commands have empty names, but
98
     * JavaScript can set the name. A name may be used as a target for a request.
99
     *
100
     * @return the name
101
     */
102
    public String getName() {
103
        return _name;
1✔
104
    }
105

106
    /**
107
     * Returns the web response that contained the script which opened this window.
108
     *
109
     * @return the opener
110
     */
111
    public WebResponse getOpener() {
112
        return _opener;
1✔
113
    }
114

115
    /**
116
     * Submits a GET method request and returns a response.
117
     *
118
     * @param urlString
119
     *            the url string
120
     *
121
     * @return the response
122
     *
123
     * @throws IOException
124
     *             Signals that an I/O exception has occurred.
125
     *
126
     * @exception SAXException
127
     *                thrown if there is an error parsing the retrieved page
128
     */
129
    public WebResponse getResponse(String urlString) throws IOException, SAXException {
130
        return getResponse(new GetMethodWebRequest(urlString));
1✔
131
    }
132

133
    /**
134
     * Submits a web request and returns a response. This is an alternate name for the getResponse method.
135
     *
136
     * @param request
137
     *            the request
138
     *
139
     * @return the WebResponse or null
140
     *
141
     * @throws IOException
142
     *             Signals that an I/O exception has occurred.
143
     * @throws SAXException
144
     *             the SAX exception
145
     */
146
    public WebResponse sendRequest(WebRequest request) throws IOException, SAXException {
147
        return getResponse(request);
1✔
148
    }
149

150
    /**
151
     * Submits a web request and returns a response, using all state developed so far as stored in cookies as requested
152
     * by the server. see patch [ 1155415 ] Handle redirect instructions which can lead to a loop
153
     *
154
     * @param request
155
     *            the request
156
     *
157
     * @return the WebResponse or null
158
     *
159
     * @throws IOException
160
     *             Signals that an I/O exception has occurred.
161
     *
162
     * @exception SAXException
163
     *                thrown if there is an error parsing the retrieved page
164
     */
165
    public WebResponse getResponse(WebRequest request) throws IOException, SAXException {
166
        // Need to have some sort of ExecuteAroundMethod to ensure that the
167
        // redirects data structure gets cleared down upon exit - not
168
        // straightforward, since this could be a recursive call
169
        if (_isInitialRequest) {
1✔
170
            _initialRequest = request;
1✔
171
            _isInitialRequest = false;
1✔
172
        }
173

174
        WebResponse result = null;
1✔
175

176
        try {
177
            final RequestContext requestContext = new RequestContext();
1✔
178
            final WebResponse response = getSubframeResponse(request, requestContext);
1✔
179
            requestContext.runScripts();
1✔
180
            // javascript might replace the response in its frame
181
            result = response == null ? null : response.getWindow().getFrameContents(response.getFrame());
1✔
182
        } finally {
183
            if (null != request && request.equals(_initialRequest)) {
1!
184
                _redirects.clear();
1✔
185
                _initialRequest = null;
1✔
186
                _isInitialRequest = true;
1✔
187
            }
188
        }
189
        return result;
1✔
190
    }
191

192
    /**
193
     * get a Response from a SubFrame.
194
     *
195
     * @param request
196
     *            the request
197
     * @param requestContext
198
     *            the request context
199
     *
200
     * @return the WebResponse or null
201
     *
202
     * @throws IOException
203
     *             Signals that an I/O exception has occurred.
204
     * @throws SAXException
205
     *             the SAX exception
206
     */
207
    WebResponse getSubframeResponse(WebRequest request, RequestContext requestContext)
208
            throws IOException, SAXException {
209
        WebResponse response = getResource(request);
1✔
210

211
        return response == null ? null : updateWindow(request.getTarget(), response, requestContext);
1✔
212
    }
213

214
    /**
215
     * Updates this web client based on a received response. This includes updating cookies and frames.
216
     *
217
     * @param requestTarget
218
     *            the request target
219
     * @param response
220
     *            the response
221
     * @param requestContext
222
     *            the request context
223
     *
224
     * @return the web response
225
     *
226
     * @throws IOException
227
     *             Signals that an I/O exception has occurred.
228
     * @throws SAXException
229
     *             the SAX exception
230
     */
231
    WebResponse updateWindow(String requestTarget, WebResponse response, RequestContext requestContext)
232
            throws IOException, SAXException {
233
        _client.updateClient(response);
1✔
234
        if (getClient().getClientProperties().isAutoRefresh() && response.getRefreshRequest() != null) {
1✔
235
            WebRequest request = response.getRefreshRequest();
1✔
236
            return getResponse(request);
1✔
237
        }
238
        if (shouldFollowRedirect(response)) {
1✔
239
            delay(HttpUnitOptions.getRedirectDelay());
1✔
240
            return getResponse(new RedirectWebRequest(response));
1✔
241
        }
242
        _client.updateFrameContents(this, requestTarget, response, requestContext);
1✔
243
        return response;
1✔
244
    }
245

246
    /**
247
     * Returns the resource specified by the request. Does not update the window or load included framesets. May return
248
     * null if the resource is a JavaScript URL which would normally leave the client unchanged.
249
     *
250
     * @param request
251
     *            the request
252
     *
253
     * @return the resource
254
     *
255
     * @throws IOException
256
     *             Signals that an I/O exception has occurred.
257
     */
258
    public WebResponse getResource(WebRequest request) throws IOException {
259
        _client.tellListeners(request);
1✔
260

261
        WebResponse response = null;
1✔
262
        String urlString = request.getURLString().trim();
1✔
263
        FrameSelector targetFrame = _frameContents.getTargetFrame(request);
1✔
264
        if (urlString.startsWith("about:")) {
1✔
265
            response = new DefaultWebResponse(_client, targetFrame, null, "");
1✔
266
        } else if (!HttpUnitUtils.isJavaScriptURL(urlString)) {
1✔
267
            response = _client.createResponse(request, targetFrame);
1✔
268
        } else {
269
            ScriptingHandler handler = request.getSourceScriptingHandler();
1✔
270
            if (handler == null) {
1!
271
                handler = getCurrentPage().getScriptingHandler();
1✔
272
            }
273
            Object result = handler.evaluateExpression(urlString);
1✔
274
            if (result != null) {
1✔
275
                response = new DefaultWebResponse(_client, targetFrame, request.getURL(), result.toString());
1✔
276
            }
277
        }
278

279
        if (response != null) {
1✔
280
            _client.tellListeners(response);
1✔
281
        }
282
        return response;
1✔
283
    }
284

285
    /**
286
     * Returns the name of the currently active frames.
287
     *
288
     * @return the frame names
289
     */
290
    public String[] getFrameNames() {
291
        final List<String> names = _frameContents.getActiveFrameNames();
1✔
292
        return names.toArray(new String[names.size()]);
1✔
293
    }
294

295
    /**
296
     * Returns true if the specified frame name is defined in this window.
297
     *
298
     * @param frameName
299
     *            the frame name
300
     *
301
     * @return true, if successful
302
     */
303
    public boolean hasFrame(String frameName) {
304
        return _frameContents.get(frameName) != null;
×
305
    }
306

307
    /**
308
     * Checks for frame.
309
     *
310
     * @param frame
311
     *            the frame
312
     *
313
     * @return true, if successful
314
     */
315
    boolean hasFrame(FrameSelector frame) {
316
        return _frameContents.get(frame) != null;
1✔
317
    }
318

319
    /**
320
     * Returns the response associated with the specified frame name. Throws a runtime exception if no matching frame is
321
     * defined.
322
     *
323
     * @param frameName
324
     *            the frame name
325
     *
326
     * @return the frame contents
327
     */
328
    public WebResponse getFrameContents(String frameName) {
329
        WebResponse response = _frameContents.get(frameName);
1✔
330
        if (response == null) {
1!
331
            throw new NoSuchFrameException(frameName);
×
332
        }
333
        return response;
1✔
334
    }
335

336
    /**
337
     * Returns the response associated with the specified frame target. Throws a runtime exception if no matching frame
338
     * is defined.
339
     *
340
     * @param targetFrame
341
     *            the target frame
342
     *
343
     * @return the frame contents
344
     */
345
    WebResponse getFrameContents(FrameSelector targetFrame) {
346
        return _frameContents.getFrameContents(targetFrame);
1✔
347
    }
348

349
    /**
350
     * Gets the subframe contents.
351
     *
352
     * @param frame
353
     *            the frame
354
     * @param subFrameName
355
     *            the sub frame name
356
     *
357
     * @return the subframe contents
358
     */
359
    WebResponse getSubframeContents(FrameSelector frame, String subFrameName) {
360
        return _frameContents.getSubframeContents(frame, subFrameName);
1✔
361
    }
362

363
    /**
364
     * Gets the parent frame contents.
365
     *
366
     * @param frame
367
     *            the frame
368
     *
369
     * @return the parent frame contents
370
     */
371
    WebResponse getParentFrameContents(FrameSelector frame) {
372
        return _frameContents.getParentFrameContents(frame);
1✔
373
    }
374

375
    /**
376
     * Returns the response representing the main page in this window.
377
     *
378
     * @return the current page
379
     */
380
    public WebResponse getCurrentPage() {
381
        return getFrameContents(WebRequest.TOP_FRAME);
1✔
382
    }
383

384
    /**
385
     * construct a WebWindow from a given client.
386
     *
387
     * @param client
388
     *            - the client to construct me from
389
     */
390
    WebWindow(WebClient client) {
1✔
391
        _client = client;
1✔
392
        _frameContents = new FrameHolder(this);
1✔
393
        _name = NO_NAME + _client.getOpenWindows().length;
1✔
394
        _redirects = new Hashtable<>();
1✔
395
    }
1✔
396

397
    /**
398
     * Instantiates a new web window.
399
     *
400
     * @param client
401
     *            the client
402
     * @param opener
403
     *            the opener
404
     */
405
    WebWindow(WebClient client, WebResponse opener) {
406
        this(client);
1✔
407
        _opener = opener;
1✔
408
    }
1✔
409

410
    /**
411
     * Update frame contents.
412
     *
413
     * @param response
414
     *            the response
415
     * @param requestContext
416
     *            the request context
417
     *
418
     * @throws IOException
419
     *             Signals that an I/O exception has occurred.
420
     * @throws SAXException
421
     *             the SAX exception
422
     */
423
    void updateFrameContents(WebResponse response, RequestContext requestContext) throws IOException, SAXException {
424
        response.setWindow(this);
1✔
425
        _frameContents.updateFrames(response, response.getFrame(), requestContext);
1✔
426
    }
1✔
427

428
    /**
429
     * Sets the name.
430
     *
431
     * @param name
432
     *            the new name
433
     */
434
    void setName(String name) {
435
        _name = name;
1✔
436
    }
1✔
437

438
    /**
439
     * Delays the specified amount of time.
440
     *
441
     * @param numMilliseconds
442
     *            the num milliseconds
443
     */
444
    private void delay(int numMilliseconds) {
445
        if (numMilliseconds == 0) {
1!
446
            return;
1✔
447
        }
448
        try {
449
            Thread.sleep(numMilliseconds);
×
450
        } catch (InterruptedException e) {
×
451
            // ignore the exception
452
            Thread.interrupted();
×
453
        }
×
454
    }
×
455

456
    /**
457
     * check whether redirect is configured.
458
     *
459
     * @param response
460
     *            the response
461
     *
462
     * @return true, if successful
463
     */
464
    private boolean redirectConfigured(WebResponse response) {
465
        boolean isAutoredirect = getClient().getClientProperties().isAutoRedirect();
1✔
466
        boolean hasLocation = response.getHeaderField("Location") != null;
1✔
467
        int responseCode = response.getResponseCode();
1✔
468
        return isAutoredirect && responseCode >= HttpURLConnection.HTTP_MOVED_PERM
1!
469
                && responseCode <= HttpURLConnection.HTTP_MOVED_TEMP && hasLocation;
470
    }
471

472
    /**
473
     * check wether we should follow the redirect given in the response make sure we don't run into a recursion.
474
     *
475
     * @param response
476
     *            the response
477
     *
478
     * @return true, if successful
479
     */
480
    private boolean shouldFollowRedirect(WebResponse response) {
481
        // first check whether redirect is configured for this response
482
        // this is the old pre [ 1155415 ] Handle redirect instructions which can lead to a loop
483
        // shouldFollowRedirect method - just renamed
484
        if (!redirectConfigured(response)) {
1✔
485
            return false;
1✔
486
        }
487
        // now do the recursion check
488
        String redirectLocation = response.getHeaderField("Location");
1✔
489

490
        URL url = null;
1✔
491

492
        try {
493
            if (redirectLocation != null) {
1!
494
                url = new URL(response.getURL(), redirectLocation);
1✔
495
            }
496
        } catch (MalformedURLException e) {
1✔
497
            // Fall through and allow existing exception handling code deal
498
            // with any exception - we don't know at this stage whether it is
499
            // a redirect instruction, although it is highly likely, given
500
            // there is a location header present in the response!
501
        }
1✔
502

503
        switch (response.getResponseCode()) {
1!
504
            case HttpURLConnection.HTTP_MOVED_PERM:
505
            case HttpURLConnection.HTTP_MOVED_TEMP: // Fall through
506
                int count = 0;
1✔
507
                if (null != url) {
1✔
508
                    Integer value = (Integer) _redirects.get(url);
1✔
509
                    if (null != value) {
1✔
510
                        // We have already been instructed to redirect to that
511
                        // location in the course of this attempt to resolve the
512
                        // resource
513

514
                        count = value.intValue();
1✔
515

516
                        int maxRedirects = getClient().getClientProperties().getMaxRedirects();
1✔
517

518
                        if (count == maxRedirects) {
1✔
519
                            throw new RecursiveRedirectionException(url, "Maximum number of redirects exceeded");
1✔
520
                        }
521
                    }
522

523
                    count++;
1✔
524
                    _redirects.put(url, Integer.valueOf(count));
1✔
525
                }
526
                break;
527
        }
528
        return redirectLocation != null;
1!
529
    }
530

531
    /**
532
     * Gets the top frame.
533
     *
534
     * @return the top frame
535
     */
536
    FrameSelector getTopFrame() {
537
        return _frameContents.getTopFrame();
1✔
538
    }
539

540
    /**
541
     * Gets the frame.
542
     *
543
     * @param target
544
     *            the target
545
     *
546
     * @return the frame
547
     */
548
    FrameSelector getFrame(String target) {
549
        return _frameContents.getFrame(target);
1✔
550
    }
551

552
}
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