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

eXist-db / exist / 40899752347

29 Oct 2026 03:15PM UTC coverage: 58.193% (+0.005%) from 58.188%
40899752347

push

github

web-flow
Merge pull request #6886 from duncdrum/bugfix/locale-root-tokens

[bugfix] Use Locale.ROOT when case-mapping tokens and protocol values

32503 of 61720 branches covered (52.66%)

Branch coverage included in aggregate %.

16 of 21 new or added lines in 14 files covered. (76.19%)

6 existing lines in 2 files now uncovered.

85588 of 141209 relevant lines covered (60.61%)

2.73 hits per line

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

0.0
/exist-core/src/main/java/org/exist/http/servlets/HttpServletRequestWrapper.java
1
/*
2
 * eXist-db Open Source Native XML Database
3
 * Copyright (C) 2001 The eXist-db Authors
4
 *
5
 * info@exist-db.org
6
 * http://www.exist-db.org
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 */
22
package org.exist.http.servlets;
23

24
import org.apache.logging.log4j.LogManager;
25
import org.apache.logging.log4j.Logger;
26
import org.exist.util.IterableEnumeration;
27
import org.exist.util.io.CachingFilterInputStream;
28
import org.exist.util.io.FilterInputStreamCache;
29
import org.exist.util.io.FilterInputStreamCacheFactory;
30

31
import jakarta.servlet.*;
32
import jakarta.servlet.http.*;
33
import java.io.*;
34
import java.net.URLDecoder;
35
import java.security.Principal;
36
import java.util.*;
37

38
import static java.nio.charset.StandardCharsets.UTF_8;
39

40
/**
41
 * A wrapper for HttpServletRequest
42
 * - differentiates between POST parameters in the URL or Content Body
43
 * - caches content Body of the POST request as read, making it available many times
44
 *
45
 * A method of differentiating between POST parameters in the URL or Content Body of the request was needed.
46
 * The standard jakarta.servlet.http.HTTPServletRequest does not differentiate between URL or content body parameters,
47
 * this class does, the type is indicated in RequestParameter.type.
48
 *
49
 * To differentiate manually we need to read the URL (getQueryString()) and the Content body (getInputStream()),
50
 * this is problematic with the standard jakarta.servlet.http.HTTPServletRequest as parameter functions (getParameterMap(), getParameterNames(), getParameter(String), getParameterValues(String))
51
 * affect the  input stream functions (getInputStream(), getReader()) and vice versa.
52
 *
53
 * This class solves this by reading the Request Parameters initially from both the URL and the Content Body of the Request
54
 * and storing them in the private variable params for later use.
55
 *
56
 * @author <a href="mailto:adam.retter@devon.gov.uk">Adam Retter</a>
57
 * @version 1.2
58
 * @serial 2018-04-03
59
 */
60

61
//TODO: check loops to make sure they only iterate as few times as needed
62
//TODO: do we need to do anything with encoding strings manually?
63

64

65
public class HttpServletRequestWrapper implements HttpServletRequest, Closeable {
66
    private static final Logger LOG = LogManager.getLogger(HttpServletRequestWrapper.class);
×
67

68
    private final HttpServletRequest request;
69
    private final String formEncoding;
70
    private final Map<String, List<RequestParameter>> params = new HashMap<>();
×
71
    private final FilterInputStreamCache cache;
72
    private final CachingFilterInputStream is;
73

74
    /**
75
     * HttpServletRequestWrapper Constructor
76
     *
77
     * @param cacheConfiguration the cache configuration
78
     * @param request The HttpServletRequest to wrap
79
     * @param formEncoding The encoding to use
80
     *
81
     * @throws IOException if an I/O error occurs
82
     */
83
    public HttpServletRequestWrapper(final FilterInputStreamCacheFactory.FilterInputStreamCacheConfiguration cacheConfiguration, final HttpServletRequest request, final String formEncoding) throws IOException {
×
84
        this.request = request;
×
85
        if (request.getCharacterEncoding() != null) {
×
86
            this.formEncoding = getCharacterEncoding();
×
87

88
        } else {
×
89
            request.setCharacterEncoding(formEncoding);
×
90
            this.formEncoding = formEncoding;
×
91
        }
92

93
        this.cache = FilterInputStreamCacheFactory.getCacheInstance(cacheConfiguration, request.getInputStream());
×
94
        this.is = new CachingFilterInputStream(cache);
×
95

96
        initialiseWrapper();
×
97
    }
×
98

99
    //Initialises the wrapper, and parameter map
100
    private void initialiseWrapper() {
101
        //Parse out parameters from the URL
102
        parseURLParameters(this.request.getQueryString());
×
103

104
        //If POST request, Parse out parameters from the Content Body
105
        if ("POST".equalsIgnoreCase(request.getMethod())) {
×
106
            //If there is some Content
107
            final int contentLength = request.getContentLength();
×
108
            if (contentLength > 0 || contentLength == -1) {
×
109
                // If a form POST , and not a document POST
110
                final String rawContentType = request.getContentType();
×
111
                if (rawContentType != null) {
×
NEW
112
                    String contentType = rawContentType.toLowerCase(Locale.ROOT);
×
113
                    final int semicolon = contentType.indexOf(';');
×
114
                    if (semicolon > 0) {
×
115
                        contentType = contentType.substring(0, semicolon).trim();
×
116
                    }
117
                    if ("application/x-www-form-urlencoded".equals(contentType)
×
118
                            && request.getHeader("ContentType") == null) {
×
119
                        //Parse out parameters from the Content Body
120
                        parseContentBodyParameters();
×
121
                    }
122
                }
123
            }
124
        }
125
    }
×
126

127
    //Stores parameters from the QueryString of the request
128
    private void parseURLParameters(final String querystring) {
129
        if (querystring != null) {
×
130
            //Parse any parameters from the URL
131
            parseParameters(querystring, RequestParameter.ParameterSource.URL);
×
132
        }
133
    }
×
134

135
    /**
136
     * Stores parameters from the Content Body of the Request
137
     */
138
    private void parseContentBodyParameters() {
139
        try (final Reader reader = getReader()) {
×
140
            final StringBuilder body = new StringBuilder();
×
141
            final char buf[] = new char[16 * 1024];
×
142
            int read = -1;
×
143
            while((read = reader.read(buf)) > -1) {
×
144
                body.append(buf, 0, read);
×
145
            }
146
            //Parse any parameters from the Content Body
147
            parseParameters(body.toString(), RequestParameter.ParameterSource.CONTENT);
×
148
        } catch (final IOException e) {
×
149
            LOG.error("Error Reading the Content Body into the buffer", e);
×
150
        }
151
    }
×
152

153
    /**
154
     * Parses Parameters into param objects and stores them in a vector in params.
155
     *
156
     * @param parameters the parameters to parse
157
     * @param type the type of the parameters
158
     */
159
    private void parseParameters(final String parameters, final RequestParameter.ParameterSource type) {
160
        //Split parameters into an array
161
        final String[] nameValuePairs = parameters.split("&");
×
162

163
        for (final String nameValuePair : nameValuePairs) {
×
164
            //Split parameter into name and value
165
            final String[] thePair = nameValuePair.split("=");
×
166

167
            try {
168
                //URL Decode the parameter name and value
169
                thePair[0] = URLDecoder.decode(thePair[0], formEncoding);
×
170
                if (thePair.length == 2) {
×
171
                    thePair[1] = URLDecoder.decode(thePair[1], formEncoding);
×
172
                }
173
            } catch (final UnsupportedEncodingException e) {
×
174
                LOG.error(e);
×
175
            }
176

177
            //Have we encountered a parameter with this name?
178
            if (params.containsKey(thePair[0])) {
×
179
                //key exists in hash map, add value and type to vector
180
                final List<RequestParameter> vecValues = params.get(thePair[0]);
×
181
                vecValues.add(new RequestParameter((thePair.length == 2 ? thePair[1] : ""), type));
×
182
                params.put(thePair[0], vecValues);
×
183
            } else {
×
184
                //not in hash map so add a vector with the initial value
185
                final List<RequestParameter> vecValues = new ArrayList<>();
×
186
                vecValues.add(new RequestParameter((thePair.length == 2 ? thePair[1] : ""), type));
×
187
                params.put(thePair[0], vecValues);
×
188
            }
189
        }
190
    }
×
191

192
    @Override
193
    public ServletInputStream getInputStream() {
194
       return new ServletInputStreamWrapper(is);
×
195
    }
196

197
    @Override
198
    public String getParameter(final String name) {
199
        //Does the parameter exist?
200
        if (params.containsKey(name)) {
×
201
            //Get the parameters vector of values
202
            final List<RequestParameter> vecParameterValues = params.get(name);
×
203

204
            //return the first value in the vector
205
            return vecParameterValues.getFirst().getValue();
×
206
        } else {
207
            return null;
×
208
        }
209
    }
210

211
    @Override
212
    public Enumeration<String> getParameterNames() {
213
        return new IterableEnumeration<>(params.keySet());
×
214
    }
215

216
    @Override
217
    public String[] getParameterValues(final String name) {
218
        //Does the parameter exist?
219
        if (params.containsKey(name)) {
×
220
            //Get the parameters vector of values
221
            final List<RequestParameter> vecParameterValues = params.get(name);
×
222

223
            //Create a string array to hold the values
224
            final String[] values = new String[vecParameterValues.size()];
×
225

226
            //Copy each value into the string array
227
            for (int i = 0; i < vecParameterValues.size(); i++) {
×
228
                values[i] = vecParameterValues.get(i).getValue();
×
229
            }
230

231
            //return the string array of values
232
            return values;
×
233
        } else {
234
            return null;
×
235
        }
236
    }
237

238
    @Override
239
    public Map<String, String[]> getParameterMap() {
240
        //Map to hold the parameters
241
        final Map<String, String[]> mapParameters = new HashMap<>();
×
242

243
        final Set<Map.Entry<String, List<RequestParameter>>> setParams = params.entrySet();
×
244

245
        //iterate through the Request Parameters
246
        for (final Map.Entry<String, List<RequestParameter>> me : setParams) {
×
247
            //Get the parameters values
248
            final List<RequestParameter> vecParamValues = me.getValue();
×
249

250
            //Create a string array to hold the parameter values
251
            final String[] values = new String[vecParamValues.size()];
×
252

253
            //Copy the parameter values into a string array
254
            int i = 0;
×
255
            for (final Iterator<RequestParameter> itParamValues = vecParamValues.iterator(); itParamValues.hasNext(); i++) {
×
256
                values[i] = itParamValues.next().getValue();
×
257
            }
258
            mapParameters.put(me.getKey(), values); //Store the parameter in a map
×
259
        }
260
        return mapParameters; //return the Map of parameters
×
261
    }
262

263
    @Override
264
    public BufferedReader getReader() throws IOException {
265
        String encoding = request.getCharacterEncoding();
×
266
        if (encoding == null) {
×
267
            encoding = UTF_8.name();
×
268
        }
269

270
        try {
271
            return new BufferedReader(new InputStreamReader(new CachingFilterInputStream(is), encoding));
×
272
        } catch (final InstantiationException e) {
×
273
            throw new IOException(e);
×
274
        }
275
    }
276

277
    /**
278
     * Similar to jakarta.servlet.http.HttpServletRequest.toString() ,
279
     * except it includes output of the Request parameters from the Request's Content Body
280
     *
281
     * @return String representation of HttpServletRequestWrapper
282
     */
283
    @Override
284
    public String toString() {
285
        final String contentType = request.getContentType();
×
286
        // If POST request AND there is some content AND its not a file upload
287
        if ("POST".equalsIgnoreCase(request.getMethod())
×
288
                && (request.getContentLength() > 0 || request.getContentLength() == -1)
×
NEW
289
                && (contentType == null || !contentType.toUpperCase(Locale.ROOT).startsWith("MULTIPART/"))) {
×
290

291
            // Also return the content parameters, these are not part
292
            // of the standard HttpServletRequest.toString() output
293
            final StringBuilder buf = new StringBuilder(request.toString());
×
294

295
            final Set<Map.Entry<String, List<RequestParameter>>> setParams = params.entrySet();
×
296

297
            for (final Map.Entry<String, List<RequestParameter>> me : setParams) {
×
298
                final List<RequestParameter> vecParamValues = me.getValue();
×
299

300
                for (final RequestParameter p : vecParamValues) {
×
301

302
                    if (p.source == RequestParameter.ParameterSource.CONTENT) {
×
303
                        if (buf.charAt(buf.length() - 1) != '\n') {
×
304
                            buf.append("&");
×
305
                        }
306
                        buf.append(me.getKey());
×
307
                        buf.append("=");
×
308
                        buf.append(p.getValue());
×
309
                    }
310
                }
311
            }
312

313
            buf.append(System.getProperty("line.separator")).append(System.getProperty("line.separator"));
×
314

315
            return buf.toString();
×
316
        } else {
317
            //Return standard HttpServletRequest.toString() output
318
            return request.toString();
×
319
        }
320
    }
321

322
    @Override
323
    public String getAuthType() {
324
        return request.getAuthType();
×
325
    }
326

327
    @Override
328
    public Cookie[] getCookies() {
329
        return request.getCookies();
×
330
    }
331

332
    @Override
333
    public long getDateHeader(final String name) {
334
        return request.getDateHeader(name);
×
335
    }
336

337
    @Override
338
    public String getHeader(final String name) {
339
        return request.getHeader(name);
×
340
    }
341

342
    @Override
343
    public Enumeration<String> getHeaders(final String name) {
344
        return request.getHeaders(name);
×
345
    }
346

347
    @Override
348
    public Enumeration<String> getHeaderNames() {
349
        return request.getHeaderNames();
×
350
    }
351

352
    @Override
353
    public int getIntHeader(final String name) {
354
        return request.getIntHeader(name);
×
355
    }
356

357
    @Override
358
    public String getMethod() {
359
        return request.getMethod();
×
360
    }
361

362
    @Override
363
    public String getPathInfo() {
364
        return request.getPathInfo();
×
365
    }
366

367
    @Override
368
    public String getPathTranslated() {
369
        return request.getPathInfo();
×
370
    }
371

372
    @Override
373
    public String getContextPath() {
374
        return request.getContextPath();
×
375
    }
376

377
    @Override
378
    public String getQueryString() {
379
        return request.getQueryString();
×
380
    }
381

382
    @Override
383
    public String getRemoteUser() {
384
        return request.getRemoteUser();
×
385
    }
386

387
    @Override
388
    public boolean isUserInRole(final String name) {
389
        return request.isUserInRole(name);
×
390
    }
391

392
    @Override
393
    public Principal getUserPrincipal() {
394
        return request.getUserPrincipal();
×
395
    }
396

397
    @Override
398
    public String getRequestedSessionId() {
399
        return request.getRequestedSessionId();
×
400
    }
401

402
    @Override
403
    public String getRequestURI() {
404
        return request.getRequestURI();
×
405
    }
406

407
    @Override
408
    public StringBuffer getRequestURL() {
409
        return request.getRequestURL();
×
410
    }
411

412
    @Override
413
    public String getServletPath() {
414
        return request.getServletPath();
×
415
    }
416

417
    @Override
418
    public HttpSession getSession(final boolean create) {
419
        return request.getSession(create);
×
420
    }
421

422
    @Override
423
    public HttpSession getSession() {
424
        return request.getSession();
×
425
    }
426

427
    @Override
428
    public String changeSessionId() {
429
        return request.changeSessionId();
×
430
    }
431

432
    @Override
433
    public boolean isRequestedSessionIdValid() {
434
        return request.isRequestedSessionIdValid();
×
435
    }
436

437
    @Override
438
    public boolean isRequestedSessionIdFromCookie() {
439
        return request.isRequestedSessionIdFromCookie();
×
440
    }
441

442
    @Override
443
    public boolean isRequestedSessionIdFromURL() {
444
        return request.isRequestedSessionIdFromURL();
×
445
    }
446

447
    @Override
448
    public boolean authenticate(final HttpServletResponse httpServletResponse) throws IOException, ServletException {
449
        return request.authenticate(httpServletResponse);
×
450
    }
451

452
    @Override
453
    public void login(final String s, final String s1) throws ServletException {
454
        request.login(s, s1);
×
455
    }
×
456

457
    @Override
458
    public void logout() throws ServletException {
459
        request.logout();
×
460
    }
×
461

462
    @Override
463
    public Collection<Part> getParts() throws IOException, ServletException {
464
        return request.getParts();
×
465
    }
466

467
    @Override
468
    public Part getPart(final String s) throws IOException, ServletException {
469
        return request.getPart(s);
×
470
    }
471

472
    @Override
473
    public <T extends HttpUpgradeHandler> T upgrade(final Class<T> clazz) throws IOException, ServletException {
474
        return request.upgrade(clazz);
×
475
    }
476

477
    @Override
478
    public Object getAttribute(final String name) {
479
        return request.getAttribute(name);
×
480
    }
481

482
    @Override
483
    public Enumeration<String> getAttributeNames() {
484
        return request.getAttributeNames();
×
485
    }
486

487
    @Override
488
    public String getCharacterEncoding() {
489
        return request.getCharacterEncoding();
×
490
    }
491

492
    @Override
493
    public void setCharacterEncoding(final String enc) throws UnsupportedEncodingException {
494
        request.setCharacterEncoding(enc);
×
495
    }
×
496

497
    @Override
498
    public int getContentLength() {
499
        return request.getContentLength();
×
500
    }
501

502
    @Override
503
    public long getContentLengthLong() {
504
        return request.getContentLengthLong();
×
505
    }
506

507
    @Override
508
    public String getContentType() {
509
        return request.getContentType();
×
510
    }
511

512
    @Override
513
    public String getProtocol() {
514
        return request.getProtocol();
×
515
    }
516

517
    @Override
518
    public String getScheme() {
519
        return request.getScheme();
×
520
    }
521

522
    @Override
523
    public String getServerName() {
524
        return request.getServerName();
×
525
    }
526

527
    @Override
528
    public int getServerPort() {
529
        return request.getServerPort();
×
530
    }
531

532
    @Override
533
    public String getRemoteAddr() {
534
        return request.getRemoteAddr();
×
535
    }
536

537
    @Override
538
    public String getRemoteHost() {
539
        return request.getRemoteHost();
×
540
    }
541

542
    @Override
543
    public void setAttribute(final String name, final Object o) {
544
        request.setAttribute(name, o);
×
545
    }
×
546

547
    @Override
548
    public void removeAttribute(final String name) {
549
        request.removeAttribute(name);
×
550
    }
×
551

552
    @Override
553
    public Locale getLocale() {
554
        return request.getLocale();
×
555
    }
556

557
    @Override
558
    public Enumeration<Locale> getLocales() {
559
        return request.getLocales();
×
560
    }
561

562
    @Override
563
    public boolean isSecure() {
564
        return request.isSecure();
×
565
    }
566

567
    @Override
568
    public RequestDispatcher getRequestDispatcher(final String name) {
569
        return request.getRequestDispatcher(name);
×
570
    }
571

572
    @Override
573
    public int getRemotePort() {
574
        return request.getRemotePort();
×
575
    }
576

577
    @Override
578
    public String getLocalName() {
579
        return request.getLocalName();
×
580
    }
581

582
    @Override
583
    public String getLocalAddr() {
584
        return request.getLocalAddr();
×
585
    }
586

587
    @Override
588
    public int getLocalPort() {
589
        return request.getLocalPort();
×
590
    }
591

592
    @Override
593
    public ServletContext getServletContext() {
594
        return request.getServletContext();
×
595
    }
596

597
    @Override
598
    public AsyncContext startAsync() throws IllegalStateException {
599
        return request.startAsync();
×
600
    }
601

602
    @Override
603
    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
604
        return request.startAsync(servletRequest, servletResponse);
×
605
    }
606

607
    @Override
608
    public boolean isAsyncStarted() {
609
        return request.isAsyncStarted();
×
610
    }
611

612
    @Override
613
    public boolean isAsyncSupported() {
614
        return request.isAsyncSupported();
×
615
    }
616

617
    @Override
618
    public AsyncContext getAsyncContext() {
619
        return request.getAsyncContext();
×
620
    }
621

622
    @Override
623
    public DispatcherType getDispatcherType() {
624
        return request.getDispatcherType();
×
625
    }
626

627
    @Override
628
    public String getRequestId() {
629
        return request.getRequestId();
×
630
    }
631

632
    @Override
633
    public String getProtocolRequestId() {
634
        return request.getProtocolRequestId();
×
635
    }
636

637
    @Override
638
    public ServletConnection getServletConnection() {
639
        return request.getServletConnection();
×
640
    }
641

642
    @Override
643
    public void close() throws IOException {
644
        this.is.close();
×
645
        this.cache.close();
×
646
    }
×
647

648
    private static class ServletInputStreamWrapper extends ServletInputStream {
649
        private final CachingFilterInputStream is;
650

651
        public ServletInputStreamWrapper(final CachingFilterInputStream is) {
×
652
           this.is = is;
×
653
        }
×
654

655
        @Override
656
        public int read() throws IOException {
657
            return is.read();
×
658
        }
659

660
        @Override
661
        public int read(final byte b[]) throws IOException {
662
            return is.read(b);
×
663
        }
664

665
        @Override
666
        public int read(final byte b[], final int off, final int len) throws IOException {
667
            return is.read(b, off, len);
×
668
        }
669

670
        @Override
671
        public int available() throws IOException {
672
            return is.available();
×
673
        }
674

675
        @Override
676
        public boolean isFinished() {
677
            try {
678
                return is.available() == 0;
×
679
            } catch (final IOException ioe) {
×
680
                LOG.error(ioe);
×
681
                return true;
×
682
            }
683
        }
684

685
        @Override
686
        public boolean isReady() {
687
            return true;
×
688
        }
689

690
        @Override
691
        public void setReadListener(final ReadListener readListener) {
692
            throw new UnsupportedOperationException();
×
693
        }
694
    }
695

696
    /**
697
     * Simple class to hold the value and type of a request parameter
698
     */
699
    private static class RequestParameter {
700
        enum ParameterSource {
×
701
            URL,        //parameter from the URL of the request
×
702
            CONTENT     //parameter from the Content of the request
×
703
        }
704

705
        private final String value;
706
        private final ParameterSource source;
707

708
        /**
709
         * RequestParameter Constructor
710
         *
711
         * @param value  Value of the Request Parameter
712
         * @param source Source of the Request Parameter, URL (1) or Content (2)
713
         */
714
        RequestParameter(final String value, final ParameterSource source) {
×
715
            this.value = value;
×
716
            this.source = source;
×
717
        }
×
718

719
        /**
720
         * Request parameter value accessor
721
         *
722
         * @return Value of Request parameter
723
         */
724
        public String getValue() {
725
            return (value);
×
726
        }
727

728
        /**
729
         * Request parameter source accessor
730
         *
731
         * @return Source of Request parameter
732
         */
733
        public ParameterSource getSource() {
734
            return (source);
×
735
        }
736
    }
737
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc