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

hazendaz / httpunit / 346

07 Jun 2025 10:38PM UTC coverage: 80.553% (-0.02%) from 80.575%
346

push

github

hazendaz
Add thread interrupted

3216 of 4105 branches covered (78.34%)

Branch coverage included in aggregate %.

1 of 5 new or added lines in 3 files covered. (20.0%)

23 existing lines in 1 file now uncovered.

8254 of 10134 relevant lines covered (81.45%)

0.81 hits per line

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

91.35
/src/main/java/com/meterware/servletunit/WebApplication.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.servletunit;
21

22
import com.meterware.httpunit.HttpInternalErrorException;
23
import com.meterware.httpunit.HttpNotFoundException;
24
import com.meterware.httpunit.HttpUnitUtils;
25

26
import jakarta.servlet.*;
27
import jakarta.servlet.http.*;
28

29
import java.io.File;
30
import java.io.IOException;
31
import java.lang.reflect.InvocationTargetException;
32
import java.net.MalformedURLException;
33
import java.net.URL;
34
import java.util.ArrayList;
35
import java.util.Collection;
36
import java.util.Collections;
37
import java.util.Dictionary;
38
import java.util.HashMap;
39
import java.util.Hashtable;
40
import java.util.Iterator;
41
import java.util.List;
42
import java.util.ListIterator;
43
import java.util.Map;
44

45
import org.w3c.dom.Document;
46
import org.w3c.dom.Element;
47
import org.w3c.dom.NodeList;
48
import org.xml.sax.SAXException;
49

50
/**
51
 * This class represents the information recorded about a single web application. It is usually extracted from web.xml.
52
 *
53
 * @author <a href="mailto:russgold@httpunit.org">Russell Gold</a>
54
 * @author <a href="balld@webslingerZ.com">Donald Ball</a>
55
 * @author <a href="jaydunning@users.sourceforge.net">Jay Dunning</a>
56
 **/
57
class WebApplication implements SessionListenerDispatcher {
58

59
    private static final SecurityConstraint NULL_SECURITY_CONSTRAINT = new NullSecurityConstraint();
1✔
60

61
    private final ServletConfiguration SECURITY_CHECK_CONFIGURATION = new ServletConfiguration(
1✔
62
            SecurityCheckServlet.class.getName());
1✔
63

64
    private final WebResourceMapping SECURITY_CHECK_MAPPING = new WebResourceMapping(SECURITY_CHECK_CONFIGURATION);
1✔
65

66
    /** A mapping of resource names to servlet configurations. **/
67
    private WebResourceMap _servletMapping = new WebResourceMap();
1✔
68

69
    /** A mapping of filter names to FilterConfigurations */
70
    private Hashtable _filters = new Hashtable<>();
1✔
71

72
    /** A mapping of servlet names to ServletConfigurations */
73
    private Hashtable _servlets = new Hashtable<>();
1✔
74

75
    /** A mapping of resource names to filter configurations. **/
76
    private FilterUrlMap _filterUrlMapping = new FilterUrlMap();
1✔
77

78
    /** A mapping of servlet names to filter configurations. **/
79
    private Hashtable _filterMapping = new Hashtable<>();
1✔
80

81
    private List<SecurityConstraint> _securityConstraints = new ArrayList<>();
1✔
82

83
    private List<ServletContextListener> _contextListeners = new ArrayList<>();
1✔
84

85
    private List<ServletContextAttributeListener> _contextAttributeListeners = new ArrayList<>();
1✔
86

87
    private List<HttpSessionListener> _sessionListeners = new ArrayList<>();
1✔
88

89
    private List<HttpSessionAttributeListener> _sessionAttributeListeners = new ArrayList<>();
1✔
90

91
    private boolean _useBasicAuthentication;
92

93
    private boolean _useFormAuthentication;
94

95
    private String _authenticationRealm = "";
1✔
96

97
    private URL _loginURL;
98

99
    private URL _errorURL;
100

101
    private Hashtable _contextParameters = new Hashtable<>();
1✔
102

103
    private File _contextDir = null;
1✔
104

105
    private String _contextPath = null;
1✔
106

107
    private ServletUnitServletContext _servletContext;
108

109
    private String _displayName;
110

111
    /**
112
     * Constructs a default application spec with no information.
113
     */
114
    WebApplication() {
1✔
115
        _contextPath = "";
1✔
116
    }
1✔
117

118
    /**
119
     * Constructs an application spec from an XML document.
120
     */
121
    WebApplication(Document document) throws MalformedURLException, SAXException {
122
        this(document, null, "");
1✔
123
    }
1✔
124

125
    /**
126
     * Constructs an application spec from an XML document.
127
     */
128
    WebApplication(Document document, String contextPath) throws MalformedURLException, SAXException {
129
        this(document, null, contextPath);
1✔
130
    }
1✔
131

132
    /**
133
     * Constructs an application spec from an XML document.
134
     */
135
    WebApplication(Document document, File file, String contextPath) throws MalformedURLException, SAXException {
1✔
136
        if (contextPath != null && !contextPath.isEmpty() && !contextPath.startsWith("/")) {
1!
137
            throw new IllegalArgumentException("Context path " + contextPath + " must start with '/'");
×
138
        }
139
        _contextDir = file;
1✔
140
        _contextPath = contextPath == null ? "" : contextPath;
1✔
141
        NodeList nl = document.getElementsByTagName("display-name");
1✔
142
        if (nl.getLength() > 0) {
1✔
143
            _displayName = XMLUtils.getTextValue(nl.item(0)).trim();
1✔
144
        }
145

146
        registerServlets(document);
1✔
147
        registerFilters(document);
1✔
148
        extractSecurityConstraints(document);
1✔
149
        extractContextParameters(document);
1✔
150
        extractLoginConfiguration(document);
1✔
151
        extractListeners(document);
1✔
152
        notifyContextInitialized();
1✔
153
        _servletMapping.autoLoadServlets();
1✔
154
    }
1✔
155

156
    private void extractListeners(Document document) throws SAXException {
157
        NodeList nl = document.getElementsByTagName("listener");
1✔
158
        for (int i = 0; i < nl.getLength(); i++) {
1✔
159
            String listenerName = XMLUtils.getChildNodeValue((Element) nl.item(i), "listener-class").trim();
1✔
160
            try {
161
                Object listener = Class.forName(listenerName).getDeclaredConstructor().newInstance();
1✔
162

163
                if (listener instanceof ServletContextListener) {
1✔
164
                    _contextListeners.add((ServletContextListener) listener);
1✔
165
                }
166
                if (listener instanceof ServletContextAttributeListener) {
1✔
167
                    _contextAttributeListeners.add((ServletContextAttributeListener) listener);
1✔
168
                }
169
                if (listener instanceof HttpSessionListener) {
1✔
170
                    _sessionListeners.add((HttpSessionListener) listener);
1✔
171
                }
172
                if (listener instanceof HttpSessionAttributeListener) {
1✔
173
                    _sessionAttributeListeners.add((HttpSessionAttributeListener) listener);
1✔
174
                }
175
            } catch (Throwable e) {
×
176
                throw new RuntimeException("Unable to load context listener " + listenerName + ": " + e.toString());
×
177
            }
1✔
178
        }
179
    }
1✔
180

181
    private void notifyContextInitialized() {
182
        ServletContextEvent event = new ServletContextEvent(getServletContext());
1✔
183

184
        for (Iterator<ServletContextListener> i = _contextListeners.iterator(); i.hasNext();) {
1✔
185
            ServletContextListener listener = i.next();
1✔
186
            listener.contextInitialized(event);
1✔
187
        }
1✔
188
    }
1✔
189

190
    void shutDown() {
191
        destroyServlets();
1✔
192
        notifyContextDestroyed();
1✔
193
    }
1✔
194

195
    private void notifyContextDestroyed() {
196
        ServletContextEvent event = new ServletContextEvent(getServletContext());
1✔
197

198
        for (ListIterator<ServletContextListener> i = _contextListeners.listIterator(_contextListeners.size()); i
1✔
199
                .hasPrevious();) {
1✔
200
            ServletContextListener listener = i.previous();
1✔
201
            listener.contextDestroyed(event);
1✔
202
        }
1✔
203
    }
1✔
204

205
    void sendAttributeAdded(String name, Object value) {
206
        ServletContextAttributeEvent event = new ServletContextAttributeEvent(getServletContext(), name, value);
1✔
207

208
        for (Iterator<ServletContextAttributeListener> i = _contextAttributeListeners.iterator(); i.hasNext();) {
1✔
209
            ServletContextAttributeListener listener = i.next();
1✔
210
            listener.attributeAdded(event);
1✔
211
        }
1✔
212
    }
1✔
213

214
    void sendAttributeReplaced(String name, Object value) {
215
        ServletContextAttributeEvent event = new ServletContextAttributeEvent(getServletContext(), name, value);
1✔
216

217
        for (Iterator<ServletContextAttributeListener> i = _contextAttributeListeners.iterator(); i.hasNext();) {
1✔
218
            ServletContextAttributeListener listener = i.next();
1✔
219
            listener.attributeReplaced(event);
1✔
220
        }
1✔
221
    }
1✔
222

223
    void sendAttributeRemoved(String name, Object value) {
224
        ServletContextAttributeEvent event = new ServletContextAttributeEvent(getServletContext(), name, value);
1✔
225

226
        for (Iterator<ServletContextAttributeListener> i = _contextAttributeListeners.iterator(); i.hasNext();) {
1✔
227
            ServletContextAttributeListener listener = i.next();
1✔
228
            listener.attributeRemoved(event);
1✔
229
        }
1✔
230
    }
1✔
231

232
    private void extractSecurityConstraints(Document document) throws SAXException {
233
        NodeList nl = document.getElementsByTagName("security-constraint");
1✔
234
        for (int i = 0; i < nl.getLength(); i++) {
1✔
235
            _securityConstraints.add(new SecurityConstraintImpl((Element) nl.item(i)));
1✔
236
        }
237
    }
1✔
238

239
    String getContextPath() {
240
        return _contextPath;
1✔
241
    }
242

243
    ServletContext getServletContext() {
244
        if (_servletContext == null) {
1✔
245
            _servletContext = new ServletUnitServletContext(this);
1✔
246
        }
247
        return _servletContext;
1✔
248
    }
249

250
    /**
251
     * Registers a servlet class to be run.
252
     **/
253
    void registerServlet(String resourceName, String servletClassName, Hashtable initParams) {
254
        registerServlet(resourceName, new ServletConfiguration(servletClassName, initParams));
1✔
255
    }
1✔
256

257
    /**
258
     * Registers a servlet to be run.
259
     **/
260
    void registerServlet(String resourceName, ServletConfiguration servletConfiguration) {
261
        // FIXME - shouldn't everything start with one or the other?
262
        if (!resourceName.startsWith("/") && !resourceName.startsWith("*")) {
1✔
263
            resourceName = "/" + resourceName;
1✔
264
        }
265
        _servletMapping.put(resourceName, servletConfiguration);
1✔
266
    }
1✔
267

268
    /**
269
     * Calls the destroy method for every active servlet.
270
     */
271
    void destroyServlets() {
272
        _servletMapping.destroyWebResources();
1✔
273
    }
1✔
274

275
    ServletMetaData getServletRequest(URL url) {
276
        return _servletMapping.get(url);
1✔
277
    }
278

279
    /**
280
     * Returns true if this application uses Basic Authentication.
281
     */
282
    boolean usesBasicAuthentication() {
283
        return _useBasicAuthentication;
1✔
284
    }
285

286
    /**
287
     * Returns true if this application uses form-based authentication.
288
     */
289
    boolean usesFormAuthentication() {
290
        return _useFormAuthentication;
1✔
291
    }
292

293
    String getAuthenticationRealm() {
294
        return _authenticationRealm;
1✔
295
    }
296

297
    URL getLoginURL() {
298
        return _loginURL;
1✔
299
    }
300

301
    URL getErrorURL() {
302
        return _errorURL;
1✔
303
    }
304

305
    /**
306
     * Returns true if the specified path may only be accesses by an authorized user.
307
     *
308
     * @param url
309
     *            the application-relative path of the URL
310
     */
311
    boolean requiresAuthorization(URL url) {
312
        String result;
313
        String file = url.getFile();
1✔
314
        if (_contextPath.equals("")) {
1✔
315
            result = file;
1✔
316
        } else if (file.startsWith(_contextPath)) {
1!
317
            result = file.substring(_contextPath.length());
1✔
318
        } else {
UNCOV
319
            result = null;
×
320
        }
321
        return getControllingConstraint(result) != NULL_SECURITY_CONSTRAINT;
1✔
322
    }
323

324
    /**
325
     * Returns an array containing the roles permitted to access the specified URL.
326
     */
327
    String[] getPermittedRoles(URL url) {
328
        String result;
329
        String file = url.getFile();
1✔
330
        if (_contextPath.equals("")) {
1✔
331
            result = file;
1✔
332
        } else if (file.startsWith(_contextPath)) {
1!
333
            result = file.substring(_contextPath.length());
1✔
334
        } else {
UNCOV
335
            result = null;
×
336
        }
337
        return getControllingConstraint(result).getPermittedRoles();
1✔
338
    }
339

340
    private SecurityConstraint getControllingConstraint(String urlPath) {
341
        for (SecurityConstraint sc : _securityConstraints) {
1✔
342
            if (sc.controlsPath(urlPath)) {
1✔
343
                return sc;
1✔
344
            }
345
        }
1✔
346
        return NULL_SECURITY_CONSTRAINT;
1✔
347
    }
348

349
    File getResourceFile(String path) {
350
        String relativePath = path.startsWith("/") ? path.substring(1) : path;
1✔
351
        if (_contextDir == null) {
1✔
352
            return new File(relativePath);
1✔
353
        }
354
        return new File(_contextDir, relativePath);
1✔
355
    }
356

357
    Hashtable getContextParameters() {
358
        return _contextParameters;
1✔
359
    }
360

361
    // ---------------------------------------- SessionListenerDispatcher methods
362
    // -------------------------------------------
363

364
    @Override
365
    public void sendSessionCreated(HttpSession session) {
366
        HttpSessionEvent event = new HttpSessionEvent(session);
1✔
367

368
        for (HttpSessionListener listener : _sessionListeners) {
1✔
369
            listener.sessionCreated(event);
1✔
370
        }
1✔
371
    }
1✔
372

373
    @Override
374
    public void sendSessionDestroyed(HttpSession session) {
375
        HttpSessionEvent event = new HttpSessionEvent(session);
1✔
376

377
        for (HttpSessionListener listener : _sessionListeners) {
1✔
378
            listener.sessionDestroyed(event);
1✔
379
        }
1✔
380
    }
1✔
381

382
    @Override
383
    public void sendAttributeAdded(HttpSession session, String name, Object value) {
384
        HttpSessionBindingEvent event = new HttpSessionBindingEvent(session, name, value);
1✔
385

386
        for (HttpSessionAttributeListener listener : _sessionAttributeListeners) {
1✔
387
            listener.attributeAdded(event);
1✔
388
        }
1✔
389
    }
1✔
390

391
    @Override
392
    public void sendAttributeReplaced(HttpSession session, String name, Object oldValue) {
393
        HttpSessionBindingEvent event = new HttpSessionBindingEvent(session, name, oldValue);
1✔
394

395
        for (HttpSessionAttributeListener listener : _sessionAttributeListeners) {
1✔
396
            listener.attributeReplaced(event);
1✔
397
        }
1✔
398
    }
1✔
399

400
    @Override
401
    public void sendAttributeRemoved(HttpSession session, String name, Object oldValue) {
402
        HttpSessionBindingEvent event = new HttpSessionBindingEvent(session, name, oldValue);
1✔
403

404
        for (HttpSessionAttributeListener listener : _sessionAttributeListeners) {
1✔
405
            listener.attributeRemoved(event);
1✔
406
        }
1✔
407
    }
1✔
408

409
    // --------------------------------------------------- private members
410
    // --------------------------------------------------
411

412
    private void registerFilters(Document document) throws SAXException {
413
        Hashtable nameToClass = new Hashtable<>();
1✔
414
        NodeList nl = document.getElementsByTagName("filter");
1✔
415
        for (int i = 0; i < nl.getLength(); i++) {
1✔
416
            registerFilterClass(nameToClass, (Element) nl.item(i));
1✔
417
        }
418
        nl = document.getElementsByTagName("filter-mapping");
1✔
419
        for (int i = 0; i < nl.getLength(); i++) {
1✔
420
            registerFilter(nameToClass, (Element) nl.item(i));
1✔
421
        }
422
        this._filters = nameToClass;
1✔
423
    }
1✔
424

425
    private void registerFilterClass(Dictionary mapping, Element filterElement) throws SAXException {
426
        String filterName = XMLUtils.getChildNodeValue(filterElement, "filter-name");
1✔
427
        mapping.put(filterName, new FilterConfiguration(filterName, filterElement));
1✔
428
    }
1✔
429

430
    private void registerFilter(Dictionary mapping, Element filterElement) throws SAXException {
431
        if (XMLUtils.hasChildNode(filterElement, "servlet-name")) {
1✔
432
            registerFilterForServlet(XMLUtils.getChildNodeValue(filterElement, "servlet-name"),
1✔
433
                    (FilterConfiguration) mapping.get(XMLUtils.getChildNodeValue(filterElement, "filter-name")));
1✔
434
        }
435
        if (XMLUtils.hasChildNode(filterElement, "url-pattern")) {
1✔
436
            registerFilterForUrl(XMLUtils.getChildNodeValue(filterElement, "url-pattern"),
1✔
437
                    (FilterConfiguration) mapping.get(XMLUtils.getChildNodeValue(filterElement, "filter-name")));
1✔
438
        }
439
    }
1✔
440

441
    private void registerFilterForUrl(String resourceName, FilterConfiguration filterConfiguration) {
442
        _filterUrlMapping.put(resourceName, filterConfiguration);
1✔
443
    }
1✔
444

445
    private void registerFilterForServlet(String servletName, FilterConfiguration filterConfiguration) {
446
        List list = (List) _filterMapping.get(servletName);
1✔
447
        if (list == null) {
1✔
448
            list = new ArrayList<>();
1✔
449
            _filterMapping.put(servletName, list);
1✔
450
        }
451
        list.add(filterConfiguration);
1✔
452
    }
1✔
453

454
    private void extractLoginConfiguration(Document document) throws MalformedURLException, SAXException {
455
        NodeList nl = document.getElementsByTagName("login-config");
1✔
456
        if (nl.getLength() == 1) {
1✔
457
            final Element loginConfigElement = (Element) nl.item(0);
1✔
458
            String authenticationMethod = XMLUtils.getChildNodeValue(loginConfigElement, "auth-method", "BASIC");
1✔
459
            _authenticationRealm = XMLUtils.getChildNodeValue(loginConfigElement, "realm-name", "");
1✔
460
            if (authenticationMethod.equalsIgnoreCase("BASIC")) {
1✔
461
                _useBasicAuthentication = true;
1✔
462
                if (_authenticationRealm.isEmpty()) {
1!
UNCOV
463
                    throw new SAXException("No realm specified for BASIC Authorization");
×
464
                }
465
            } else if (authenticationMethod.equalsIgnoreCase("FORM")) {
1!
466
                _useFormAuthentication = true;
1✔
467
                if (_authenticationRealm.isEmpty()) {
1!
UNCOV
468
                    throw new SAXException("No realm specified for FORM Authorization");
×
469
                }
470
                _loginURL = new URL("http", "localhost",
1✔
471
                        _contextPath + XMLUtils.getChildNodeValue(loginConfigElement, "form-login-page"));
1✔
472
                _errorURL = new URL("http", "localhost",
1✔
473
                        _contextPath + XMLUtils.getChildNodeValue(loginConfigElement, "form-error-page"));
1✔
474
            }
475
        }
476
    }
1✔
477

478
    private void registerServlets(Document document) throws SAXException {
479
        Hashtable nameToClass = new Hashtable<>();
1✔
480
        NodeList nl = document.getElementsByTagName("servlet");
1✔
481
        for (int i = 0; i < nl.getLength(); i++) {
1✔
482
            registerServletClass(nameToClass, (Element) nl.item(i));
1✔
483
        }
484
        nl = document.getElementsByTagName("servlet-mapping");
1✔
485
        for (int i = 0; i < nl.getLength(); i++) {
1✔
486
            registerServlet(nameToClass, (Element) nl.item(i));
1✔
487
        }
488
        this._servlets = nameToClass;
1✔
489
    }
1✔
490

491
    private void registerServletClass(Dictionary mapping, Element servletElement) throws SAXException {
492
        mapping.put(XMLUtils.getChildNodeValue(servletElement, "servlet-name"),
1✔
493
                new ServletConfiguration(servletElement));
494
    }
1✔
495

496
    private void registerServlet(Dictionary mapping, Element servletElement) throws SAXException {
497
        registerServlet(XMLUtils.getChildNodeValue(servletElement, "url-pattern"),
1✔
498
                (ServletConfiguration) mapping.get(XMLUtils.getChildNodeValue(servletElement, "servlet-name")));
1✔
499
    }
1✔
500

501
    private void extractContextParameters(Document document) throws SAXException {
502
        NodeList nl = document.getElementsByTagName("context-param");
1✔
503
        for (int i = 0; i < nl.getLength(); i++) {
1✔
504
            Element param = (Element) nl.item(i);
1✔
505
            String name = XMLUtils.getChildNodeValue(param, "param-name");
1✔
506
            String value = XMLUtils.getChildNodeValue(param, "param-value");
1✔
507
            _contextParameters.put(name, value);
1✔
508
        }
509
    }
1✔
510

511
    private static boolean patternMatches(String urlPattern, String urlPath) {
512
        return urlPattern.equals(urlPath);
1✔
513
    }
514

515
    String getDisplayName() {
516
        return _displayName;
1✔
517
    }
518

519
    // ============================================= SecurityCheckServlet class
520
    // =============================================
521

522
    static class SecurityCheckServlet extends HttpServlet {
1✔
523

524
        private static final long serialVersionUID = 1L;
525

526
        @Override
527
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
528
            handleLogin(req, resp);
×
UNCOV
529
        }
×
530

531
        @Override
532
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
533
            handleLogin(req, resp);
1✔
534
        }
1✔
535

536
        private void handleLogin(HttpServletRequest req, HttpServletResponse resp) throws IOException {
537
            final String username = req.getParameter("j_username");
1✔
538
            final String roleList = req.getParameter("j_password");
1✔
539
            getServletSession(req).setUserInformation(username, ServletUnitHttpRequest.toArray(roleList));
1✔
540
            resp.sendRedirect(getServletSession(req).getOriginalURL().toExternalForm());
1✔
541
        }
1✔
542

543
        private ServletUnitHttpSession getServletSession(HttpServletRequest req) {
544
            return (ServletUnitHttpSession) req.getSession();
1✔
545
        }
546

547
    }
548

549
    // ============================================= ServletConfiguration class
550
    // =============================================
551

552
    static final int DONT_AUTOLOAD = Integer.MIN_VALUE;
553
    static final int ANY_LOAD_ORDER = Integer.MAX_VALUE;
554

555
    class ServletConfiguration extends WebResourceConfiguration {
556

557
        private Servlet _servlet;
558
        private String _servletName;
559
        private String _jspFile;
560
        private int _loadOrder = DONT_AUTOLOAD;
1✔
561

562
        ServletConfiguration(String className) {
1✔
563
            super(className);
1✔
564
        }
1✔
565

566
        ServletConfiguration(String className, Hashtable initParams) {
1✔
567
            super(className, initParams);
1✔
568
        }
1✔
569

570
        ServletConfiguration(Element servletElement) throws SAXException {
1✔
571
            super(servletElement, "servlet-class", XMLUtils.getChildNodeValue(servletElement, "servlet-class",
1✔
572
                    "org.apache.jasper.servlet.JspServlet"));
573
            _servletName = XMLUtils.getChildNodeValue(servletElement, "servlet-name");
1✔
574
            _jspFile = XMLUtils.getChildNodeValue(servletElement, "jsp-file", "");
1✔
575
            if ("".equals(_jspFile)) {
1!
576
                _jspFile = null;
1✔
577
            }
578
            final NodeList loadOrder = servletElement.getElementsByTagName("load-on-startup");
1✔
579
            for (int i = 0; i < loadOrder.getLength(); i++) {
1✔
580
                String order = XMLUtils.getTextValue(loadOrder.item(i));
1✔
581
                try {
582
                    _loadOrder = Integer.parseInt(order);
1✔
583
                } catch (NumberFormatException e) {
1✔
584
                    _loadOrder = ANY_LOAD_ORDER;
1✔
585
                }
1✔
586
            }
587
        }
1✔
588

589
        synchronized Servlet getServlet()
590
                throws ClassNotFoundException, InstantiationException, IllegalAccessException, ServletException,
591
                IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
592
            if (_servlet == null) {
1✔
593
                Class servletClass = Class.forName(getClassName());
1✔
594
                _servlet = (Servlet) servletClass.getDeclaredConstructor().newInstance();
1✔
595
                String servletName = _servletName != null ? _servletName : _servlet.getClass().getName();
1✔
596
                _servlet.init(new ServletUnitServletConfig(servletName, WebApplication.this, getInitParams()));
1✔
597
            }
598

599
            return _servlet;
1✔
600
        }
601

602
        @Override
603
        synchronized void destroyResource() {
604
            if (_servlet != null) {
1✔
605
                _servlet.destroy();
1✔
606
            }
607
        }
1✔
608

609
        String getServletName() {
610
            return _servletName;
1✔
611
        }
612

613
        @Override
614
        boolean isLoadOnStartup() {
615
            return _loadOrder != DONT_AUTOLOAD;
1✔
616
        }
617

618
        public int getLoadOrder() {
619
            return _loadOrder;
1✔
620
        }
621

622
        public Object getJspFile() {
UNCOV
623
            return this._jspFile;
×
624
        }
625
    }
626

627
    // ============================================= FilterConfiguration class
628
    // =============================================
629

630
    class FilterConfiguration extends WebResourceConfiguration implements FilterMetaData {
631

632
        private Filter _filter;
633
        private String _name;
634

635
        FilterConfiguration(String name, Element filterElement) throws SAXException {
1✔
636
            super(filterElement, "filter-class");
1✔
637
            _name = name;
1✔
638
        }
1✔
639

640
        @Override
641
        public synchronized Filter getFilter() throws ServletException {
642
            try {
643
                if (_filter == null) {
1✔
644
                    Class filterClass = Class.forName(getClassName());
1✔
645
                    _filter = (Filter) filterClass.getDeclaredConstructor().newInstance();
1✔
646
                    _filter.init(new FilterConfigImpl(_name, getServletContext(), getInitParams()));
1✔
647
                }
648

649
                return _filter;
1✔
650
            } catch (ClassNotFoundException e) {
×
651
                throw new ServletException("Did not find filter class: " + getClassName());
×
652
            } catch (IllegalAccessException e) {
×
653
                throw new ServletException("Filter class " + getClassName() + " lacks a public no-arg constructor");
×
UNCOV
654
            } catch (InstantiationException | IllegalArgumentException | InvocationTargetException
×
655
                    | NoSuchMethodException | SecurityException e) {
656
                throw new ServletException("Filter class " + getClassName() + " could not be instantiated.");
×
657
            } catch (ClassCastException e) {
×
658
                throw new ServletException(
×
UNCOV
659
                        "Filter class " + getClassName() + " does not implement" + Filter.class.getName());
×
660
            }
661
        }
662

663
        @Override
664
        boolean isLoadOnStartup() {
UNCOV
665
            return false;
×
666
        }
667

668
        @Override
669
        synchronized void destroyResource() {
670
            if (_filter != null) {
×
UNCOV
671
                _filter.destroy();
×
672
            }
UNCOV
673
        }
×
674
    }
675

676
    // =================================== SecurityConstract interface and implementations
677
    // ==================================
678

679
    interface SecurityConstraint {
680

681
        boolean controlsPath(String urlPath);
682

683
        String[] getPermittedRoles();
684
    }
685

686
    static class NullSecurityConstraint implements SecurityConstraint {
1✔
687

688
        private static final String[] NO_ROLES = {};
1✔
689

690
        @Override
691
        public boolean controlsPath(String urlPath) {
UNCOV
692
            return false;
×
693
        }
694

695
        @Override
696
        public String[] getPermittedRoles() {
UNCOV
697
            return NO_ROLES;
×
698
        }
699
    }
700

701
    static class SecurityConstraintImpl implements SecurityConstraint {
702

703
        SecurityConstraintImpl(Element root) throws SAXException {
1✔
704
            final NodeList roleNames = root.getElementsByTagName("role-name");
1✔
705
            for (int i = 0; i < roleNames.getLength(); i++) {
1✔
706
                _roleList.add(XMLUtils.getTextValue(roleNames.item(i)));
1✔
707
            }
708

709
            final NodeList resources = root.getElementsByTagName("web-resource-collection");
1✔
710
            for (int i = 0; i < resources.getLength(); i++) {
1✔
711
                _resources.add(new WebResourceCollection((Element) resources.item(i)));
1✔
712
            }
713
        }
1✔
714

715
        @Override
716
        public boolean controlsPath(String urlPath) {
717
            return getMatchingCollection(urlPath) != null;
1✔
718
        }
719

720
        @Override
721
        public String[] getPermittedRoles() {
722
            if (_roles == null) {
1✔
723
                _roles = _roleList.toArray(new String[_roleList.size()]);
1✔
724
            }
725
            return _roles;
1✔
726
        }
727

728
        private String[] _roles;
729
        private List<String> _roleList = new ArrayList<>();
1✔
730
        private List<WebResourceCollection> _resources = new ArrayList<>();
1✔
731

732
        public WebResourceCollection getMatchingCollection(String urlPath) {
733
            for (WebResourceCollection wrc : _resources) {
1✔
734
                if (wrc.controlsPath(urlPath)) {
1✔
735
                    return wrc;
1✔
736
                }
737
            }
1✔
738
            return null;
1✔
739
        }
740

741
        class WebResourceCollection {
742

743
            WebResourceCollection(Element root) throws SAXException {
1✔
744
                final NodeList urlPatterns = root.getElementsByTagName("url-pattern");
1✔
745
                for (int i = 0; i < urlPatterns.getLength(); i++) {
1✔
746
                    _urlPatterns.add(XMLUtils.getTextValue(urlPatterns.item(i)));
1✔
747
                }
748
            }
1✔
749

750
            boolean controlsPath(String urlPath) {
751
                for (String pattern : _urlPatterns) {
1✔
752
                    if (patternMatches(pattern, urlPath)) {
1✔
753
                        return true;
1✔
754
                    }
755
                }
1✔
756
                return false;
1✔
757
            }
758

759
            private List<String> _urlPatterns = new ArrayList<>();
1✔
760
        }
761
    }
762

763
    static final FilterMetaData[] NO_FILTERS = {};
1✔
764

765
    static class ServletRequestImpl implements ServletMetaData {
766

767
        private URL _url;
768
        private String _fullServletPath;
769
        private WebResourceMapping _mapping;
770
        private Hashtable _filtersPerName;
771
        private FilterUrlMap _filtersPerUrl;
772

773
        ServletRequestImpl(URL url, String servletPath, WebResourceMapping mapping, Hashtable filtersPerName,
774
                FilterUrlMap filtersPerUrl) {
1✔
775
            _url = url;
1✔
776
            _fullServletPath = servletPath;
1✔
777
            _mapping = mapping;
1✔
778
            _filtersPerName = filtersPerName;
1✔
779
            _filtersPerUrl = filtersPerUrl;
1✔
780
        }
1✔
781

782
        /**
783
         * get the Servlet
784
         *
785
         * @return the Servlet from the configuration
786
         *
787
         * @throws ServletException
788
         *             - e.g. if no configuration is available
789
         */
790
        @Override
791
        public Servlet getServlet() throws ServletException {
792
            if (getConfiguration() == null) {
1✔
793
                throw new HttpNotFoundException("No servlet mapping defined", _url);
1✔
794
            }
795

796
            try {
797
                return getConfiguration().getServlet();
1✔
798
            } catch (ClassNotFoundException e) {
×
799
                throw new HttpNotFoundException(_url, e);
×
UNCOV
800
            } catch (IllegalAccessException | InstantiationException | IllegalArgumentException
×
801
                    | InvocationTargetException | NoSuchMethodException | SecurityException e) {
UNCOV
802
                throw new HttpInternalErrorException(_url, e);
×
803
            }
804
        }
805

806
        /**
807
         * get the ServletPath the decoded ServletPath
808
         */
809
        @Override
810
        public String getServletPath() {
811
            return _mapping == null ? null : HttpUnitUtils.decode(_mapping.getServletPath(_fullServletPath));
1!
812
        }
813

814
        /**
815
         * get the Path Information
816
         *
817
         * @return the decode path
818
         */
819
        @Override
820
        public String getPathInfo() {
821
            return _mapping == null ? null : HttpUnitUtils.decode(_mapping.getPathInfo(_fullServletPath));
1!
822
        }
823

824
        @Override
825
        public FilterMetaData[] getFilters() {
826
            if (getConfiguration() == null) {
1✔
827
                return NO_FILTERS;
1✔
828
            }
829

830
            List<FilterMetaData> filters = new ArrayList<>();
1✔
831
            addFiltersForPath(filters, _fullServletPath);
1✔
832
            addFiltersForServletWithName(filters, getConfiguration().getServletName());
1✔
833

834
            return filters.toArray(new FilterMetaData[filters.size()]);
1✔
835
        }
836

837
        private void addFiltersForPath(List<FilterMetaData> filters, String fullServletPath) {
838
            FilterMetaData[] matches = _filtersPerUrl.getMatchingFilters(fullServletPath);
1✔
839
            Collections.addAll(filters, matches);
1✔
840
        }
1✔
841

842
        private void addFiltersForServletWithName(List<FilterMetaData> filters, String servletName) {
843
            if (servletName == null) {
1✔
844
                return;
1✔
845
            }
846
            List<FilterMetaData> matches = (List<FilterMetaData>) _filtersPerName.get(servletName);
1✔
847
            if (matches != null) {
1✔
848
                filters.addAll(matches);
1✔
849
            }
850
        }
1✔
851

852
        private ServletConfiguration getConfiguration() {
853
            return _mapping == null ? null : (ServletConfiguration) _mapping.getConfiguration();
1✔
854
        }
855
    }
856

857
    /**
858
     * mapping for WebResources
859
     */
860
    static class WebResourceMapping {
861

862
        private WebResourceConfiguration _configuration;
863

864
        WebResourceConfiguration getConfiguration() {
865
            return _configuration;
1✔
866
        }
867

868
        WebResourceMapping(WebResourceConfiguration configuration) {
1✔
869
            _configuration = configuration;
1✔
870
        }
1✔
871

872
        /**
873
         * Returns the portion of the request path which was actually used to select the servlet. This default
874
         * implementation returns the full specified path.
875
         *
876
         * @param requestPath
877
         *            the full path of the request, relative to the application root.
878
         */
879
        String getServletPath(String requestPath) {
880
            return requestPath;
1✔
881
        }
882

883
        /**
884
         * Returns the portion of the request path which was not used to select the servlet, and can be used as data by
885
         * the servlet. This default implementation returns null.
886
         *
887
         * @param requestPath
888
         *            the full path of the request, relative to the application root.
889
         */
890
        String getPathInfo(String requestPath) {
891
            return null;
1✔
892
        }
893

894
        public void destroyResource() {
895
            getConfiguration().destroyResource();
1✔
896
        }
1✔
897
    }
898

899
    static class PartialMatchWebResourceMapping extends WebResourceMapping {
900

901
        private String _prefix;
902

903
        public PartialMatchWebResourceMapping(WebResourceConfiguration configuration, String prefix) {
904
            super(configuration);
1✔
905
            if (!prefix.endsWith("/*")) {
1!
UNCOV
906
                throw new IllegalArgumentException(prefix + " does not end with '/*'");
×
907
            }
908
            _prefix = prefix.substring(0, prefix.length() - 2);
1✔
909
        }
1✔
910

911
        @Override
912
        String getServletPath(String requestPath) {
913
            return _prefix;
1✔
914
        }
915

916
        @Override
917
        String getPathInfo(String requestPath) {
918
            return requestPath.length() > _prefix.length() ? requestPath.substring(_prefix.length()) : null;
1✔
919
        }
920
    }
921

922
    /**
923
     * A utility class for mapping web resources to url patterns. This implements the matching algorithm documented in
924
     * section 10 of the JSDK-2.2 reference.
925
     */
926
    class WebResourceMap {
1✔
927

928
        private final Map _exactMatches = new HashMap<>();
1✔
929
        private final Map _extensions = new HashMap<>();
1✔
930
        private final Map _urlTree = new HashMap<>();
1✔
931
        private WebResourceMapping _defaultMapping;
932

933
        void put(String mapping, WebResourceConfiguration configuration) {
934
            if (mapping.equals("/")) {
1✔
935
                _defaultMapping = new WebResourceMapping(configuration);
1✔
936
            } else if (mapping.startsWith("*.")) {
1✔
937
                _extensions.put(mapping.substring(2), new WebResourceMapping(configuration));
1✔
938
            } else if (!mapping.startsWith("/") || !mapping.endsWith("/*")) {
1!
939
                _exactMatches.put(mapping, new WebResourceMapping(configuration));
1✔
940
            } else {
941
                ParsedPath path = new ParsedPath(mapping);
1✔
942
                Map context = _urlTree;
1✔
943
                while (path.hasNext()) {
1!
944
                    String part = path.next();
1✔
945
                    if (part.equals("*")) {
1✔
946
                        context.put("*", new PartialMatchWebResourceMapping(configuration, mapping));
1✔
947
                        return;
1✔
948
                    }
949
                    if (!context.containsKey(part)) {
1!
950
                        context.put(part, new HashMap<>());
1✔
951
                    }
952
                    context = (Map) context.get(part);
1✔
953
                }
1✔
954
            }
955
        }
1✔
956

957
        ServletMetaData get(URL url) {
958
            String file = url.getFile();
1✔
959
            if (!file.startsWith(_contextPath)) {
1✔
960
                throw new HttpNotFoundException("File path does not begin with '" + _contextPath + "'", url);
1✔
961
            }
962

963
            String servletPath = getServletPath(file.substring(_contextPath.length()));
1✔
964

965
            if (servletPath.endsWith("j_security_check")) {
1✔
966
                return new ServletRequestImpl(url, servletPath, SECURITY_CHECK_MAPPING, _filterMapping,
1✔
967
                        _filterUrlMapping);
968
            }
969
            return new ServletRequestImpl(url, servletPath, getMapping(servletPath), _filterMapping, _filterUrlMapping);
1✔
970
        }
971

972
        private String getServletPath(String urlFile) {
973
            if (urlFile.indexOf('?') < 0) {
1✔
974
                return urlFile;
1✔
975
            }
976
            return urlFile.substring(0, urlFile.indexOf('?'));
1✔
977
        }
978

979
        public void destroyWebResources() {
980
            if (_defaultMapping != null) {
1!
UNCOV
981
                _defaultMapping.destroyResource();
×
982
            }
983
            destroyWebResources(_exactMatches);
1✔
984
            destroyWebResources(_extensions);
1✔
985
            destroyWebResources(_urlTree);
1✔
986
        }
1✔
987

988
        private void destroyWebResources(Map map) {
989
            for (Object o : map.values()) {
1✔
990
                if (o instanceof WebResourceMapping) {
1!
991
                    WebResourceMapping webResourceMapping = (WebResourceMapping) o;
1✔
992
                    webResourceMapping.destroyResource();
1✔
993
                } else {
1✔
UNCOV
994
                    destroyWebResources((Map) o);
×
995
                }
996
            }
1✔
997
        }
1✔
998

999
        void autoLoadServlets() {
1000
            ArrayList autoLoadable = new ArrayList<>();
1✔
1001
            if (_defaultMapping != null && _defaultMapping.getConfiguration().isLoadOnStartup()) {
1!
UNCOV
1002
                autoLoadable.add(_defaultMapping.getConfiguration());
×
1003
            }
1004
            collectAutoLoadableServlets(autoLoadable, _exactMatches);
1✔
1005
            collectAutoLoadableServlets(autoLoadable, _extensions);
1✔
1006
            collectAutoLoadableServlets(autoLoadable, _urlTree);
1✔
1007
            if (autoLoadable.isEmpty()) {
1✔
1008
                return;
1✔
1009
            }
1010

1011
            Collections.sort(autoLoadable, (o1, o2) -> {
1✔
1012
                ServletConfiguration sc1 = (ServletConfiguration) o1;
1✔
1013
                ServletConfiguration sc2 = (ServletConfiguration) o2;
1✔
1014
                return sc1.getLoadOrder() <= sc2.getLoadOrder() ? -1 : +1;
1✔
1015
            });
1016
            for (Iterator iterator = autoLoadable.iterator(); iterator.hasNext();) {
1✔
1017
                ServletConfiguration servletConfiguration = (ServletConfiguration) iterator.next();
1✔
1018
                try {
1019
                    servletConfiguration.getServlet();
1✔
1020
                } catch (Exception e) {
×
1021
                    HttpUnitUtils.handleException(e);
×
1022
                    throw new RuntimeException(
×
UNCOV
1023
                            "Unable to autoload servlet: " + servletConfiguration.getClassName() + ": " + e);
×
1024
                }
1✔
1025
            }
1✔
1026
        }
1✔
1027

1028
        private void collectAutoLoadableServlets(Collection collection, Map map) {
1029
            for (Object o : map.values()) {
1✔
1030
                if (o instanceof WebResourceMapping) {
1✔
1031
                    WebResourceMapping servletMapping = (WebResourceMapping) o;
1✔
1032
                    if (servletMapping.getConfiguration().isLoadOnStartup()) {
1✔
1033
                        collection.add(servletMapping.getConfiguration());
1✔
1034
                    }
1035
                } else {
1✔
1036
                    collectAutoLoadableServlets(collection, (Map) o);
1✔
1037
                }
1038
            }
1✔
1039
        }
1✔
1040

1041
        private WebResourceMapping getMapping(String url) {
1042
            if (_exactMatches.containsKey(url)) {
1✔
1043
                return (WebResourceMapping) _exactMatches.get(url);
1✔
1044
            }
1045

1046
            Map context = getContextForLongestPathPrefix(url);
1✔
1047
            if (context.containsKey("*")) {
1✔
1048
                return (WebResourceMapping) context.get("*");
1✔
1049
            }
1050

1051
            if (_extensions.containsKey(getExtension(url))) {
1✔
1052
                return (WebResourceMapping) _extensions.get(getExtension(url));
1✔
1053
            }
1054

1055
            if (_urlTree.containsKey("/")) {
1!
UNCOV
1056
                return (WebResourceMapping) _urlTree.get("/");
×
1057
            }
1058

1059
            if (_defaultMapping != null) {
1✔
1060
                return _defaultMapping;
1✔
1061
            }
1062

1063
            final String prefix = "/servlet/";
1✔
1064
            if (!url.startsWith(prefix)) {
1✔
1065
                return null;
1✔
1066
            }
1067

1068
            String className = url.substring(prefix.length());
1✔
1069
            try {
1070
                Class.forName(className);
1✔
1071
                return new WebResourceMapping(new ServletConfiguration(className));
1✔
1072
            } catch (ClassNotFoundException e) {
×
UNCOV
1073
                return null;
×
1074
            }
1075
        }
1076

1077
        private Map getContextForLongestPathPrefix(String url) {
1078
            Map context = _urlTree;
1✔
1079

1080
            ParsedPath path = new ParsedPath(url);
1✔
1081
            while (path.hasNext()) {
1✔
1082
                String part = path.next();
1✔
1083
                if (!context.containsKey(part)) {
1✔
1084
                    break;
1✔
1085
                }
1086
                context = (Map) context.get(part);
1✔
1087
            }
1✔
1088
            return context;
1✔
1089
        }
1090

1091
        private String getExtension(String url) {
1092
            int index = url.lastIndexOf('.');
1✔
1093
            if (index == -1 || index >= url.length() - 1) {
1!
1094
                return "";
1✔
1095
            }
1096
            return url.substring(index + 1);
1✔
1097
        }
1098

1099
    }
1100

1101
    /**
1102
     * return the given ServletConfiguration for the given servlet name
1103
     *
1104
     * @param servletName
1105
     *
1106
     * @return the corresponding ServletConfiguration
1107
     */
1108
    public ServletConfiguration getServletByName(String servletName) {
1109
        return (ServletConfiguration) _servlets.get(servletName);
1✔
1110
    }
1111

1112
}
1113

1114
/**
1115
 * A utility class for parsing URLs into paths
1116
 *
1117
 * @author <a href="balld@webslingerZ.com">Donald Ball</a>
1118
 */
1119
class ParsedPath {
1120

1121
    private final String path;
1122
    private int position = 0;
1✔
1123
    static final char seperator_char = '/';
1124

1125
    /**
1126
     * Creates a new parsed path for the given path value
1127
     *
1128
     * @param path
1129
     *            the path
1130
     */
1131
    ParsedPath(String path) {
1✔
1132
        if (path.charAt(0) != seperator_char) {
1!
UNCOV
1133
            throw new IllegalArgumentException("Illegal path '" + path + "', does not begin with " + seperator_char);
×
1134
        }
1135
        this.path = path;
1✔
1136
    }
1✔
1137

1138
    /**
1139
     * Returns true if there are more parts left, otherwise false
1140
     */
1141
    public final boolean hasNext() {
1142
        return position < path.length();
1✔
1143
    }
1144

1145
    /**
1146
     * Returns the next part in the path
1147
     */
1148
    public final String next() {
1149
        int offset = position + 1;
1✔
1150
        while (offset < path.length() && path.charAt(offset) != seperator_char) {
1✔
1151
            offset++;
1✔
1152
        }
1153
        String result = path.substring(position + 1, offset);
1✔
1154
        position = offset;
1✔
1155
        return result;
1✔
1156
    }
1157

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