File: ServletUnitHttpRequest.java

package info (click to toggle)
httpunit 1.7%2Bdfsg-12
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 3,564 kB
  • ctags: 6,646
  • sloc: java: 33,665; xml: 482; sh: 68; makefile: 11
file content (962 lines) | stat: -rw-r--r-- 32,687 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
package com.meterware.servletunit;
/********************************************************************************************************************
* $Id: ServletUnitHttpRequest.java 887 2008-04-03 06:20:33Z wolfgang_fahl $
*
* Copyright (c) 2000-2008, Russell Gold
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*******************************************************************************************************************/
import com.meterware.httpunit.WebRequest;
import com.meterware.httpunit.WebClient;
import com.meterware.httpunit.Base64;
import com.meterware.httpunit.HttpUnitUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.*;

import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletInputStream;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Part;


/**
 * This class represents a servlet request created from a WebRequest.
 **/
class ServletUnitHttpRequest implements HttpServletRequest {

    private ServletInputStreamImpl _inputStream;
    // TODO remove when test case for [ 1509117 ] getContentType()
    // gets available
    private String                 _contentType;
    private Vector                 _locales;
    private String                 _protocol;
    private boolean                _secure;
    private RequestContext         _requestContext;
    private String                 _charset;
    private boolean                _gotReader;
    private boolean                _gotInputStream;
    private BufferedReader         _reader;
    


    /**
     * Constructs a ServletUnitHttpRequest from a WebRequest object.
     **/
    ServletUnitHttpRequest( ServletMetaData servletRequest, WebRequest request, ServletUnitContext context, Dictionary clientHeaders, byte[] messageBody ) throws MalformedURLException {
        if (context == null) throw new IllegalArgumentException( "Context must not be null" );

        _servletRequest = servletRequest;
        _request = request;
        _context = context;
        _headers = new WebClient.HeaderDictionary();
        _headers.addEntries( clientHeaders );
        _headers.addEntries( request.getHeaders() );
        setCookiesFromHeader( _headers );
        _messageBody = messageBody;
        _protocol=request.getURL().getProtocol().toLowerCase();
        _secure = _protocol.endsWith("s" );

        _requestContext = new RequestContext( request.getURL() );
        String contentTypeHeader = (String) _headers.get( "Content-Type" );
        if (contentTypeHeader != null) {
            String[] res = HttpUnitUtils.parseContentTypeHeader( contentTypeHeader );
            _contentType = res[0];
            _charset     = res[1];
            _requestContext.setMessageEncoding( _charset );
        }
        if (_headers.get( "Content-Length") == null) _headers.put( "Content-Length", Integer.toString( messageBody.length ) );

        boolean setBody=
        // pre [ 1509117 ] getContentType()
        // _messageBody != null && (_contentType == null || _contentType.indexOf( "x-www-form-urlencoded" ) >= 0 );
        // patch version:
        	_messageBody != null && (contentTypeHeader == null ||	contentTypeHeader.indexOf( "x-www-form-urlencoded" ) >= 0 );
        if (setBody) {
            _requestContext.setMessageBody( _messageBody );
        }
    }


//----------------------------------------- HttpServletRequest methods --------------------------


    /**
     * Returns the name of the authentication scheme used to protect the servlet, for example, "BASIC" or "SSL,"
     * or null if the servlet was not protected.
     **/
    public String getAuthType() {
        return null;
    }


    /**
     * Returns the query string that is contained in the request URL after the path.
     **/
    public String getQueryString() {
        return _request.getQueryString();
    }


    /**
     * Returns an array containing all of the Cookie objects the client sent with this request.
     * This method returns null if no cookies were sent.
     **/
    public Cookie[] getCookies() {
        if (_cookies.size() == 0) {
            return null;
        } else {
            Cookie[] result = new Cookie[ _cookies.size() ];
            _cookies.copyInto( result );
            return result;
        }
    }


    /**
     * Returns the value of the specified request header as an int. If the request does not have a header
     * of the specified name, this method returns -1. If the header cannot be converted to an integer,
     * this method throws a NumberFormatException.
     **/
    public int getIntHeader( String name ) {
        return Integer.parseInt( getHeader( name ) );
    }


    /**
     * Returns the value of the specified request header as a long value that represents a Date object.
     * Use this method with headers that contain dates, such as If-Modified-Since.
     * <br>
     * The date is returned as the number of milliseconds since January 1, 1970 GMT. The header name is case insensitive.
     *
     * If the request did not have a header of the specified name, this method returns -1.
     * If the header can't be converted to a date, the method throws an IllegalArgumentException.
     **/
    public long getDateHeader( String name ) {
    	try {
    		String dateString=getHeader(name);
    		Date headerDate=new Date(dateString);
    		return headerDate.getTime();
    	} catch (Exception e) {	
        return -1;
    	}  
    }


    /**
     * Returns the value of the specified request header as a String. If the request did not include
     * a header of the specified name, this method returns null. The header name is case insensitive.
     * You can use this method with any request header.
     **/
    public String getHeader( String name ) {
        return (String) _headers.get( name );
    }


    /**
     * Returns an enumeration of all the header names this request contains. If the request has no headers,
     * this method returns an empty enumeration.
     *
     * Some servlet containers do not allow do not allow servlets to access headers using this method,
     * in which case this method returns null.
     **/
    public Enumeration getHeaderNames() {
        return _headers.keys();
    }


    /**
     * Returns the part of this request's URL that calls the servlet. This includes either the servlet name
     * or a path to the servlet, but does not include any extra path information or a query string.
     **/
    public String getServletPath() {
        return _servletRequest.getServletPath();
    }


    /**
     * Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
     **/
    public String getMethod() {
        return _request.getMethod();
    }


    /**
     * Returns any extra path information associated with the URL the client sent when it made this request.
     * The extra path information follows the servlet path but precedes the query string.
     * This method returns null if there was no extra path information.
     **/
    public String getPathInfo() {
        return _servletRequest.getPathInfo();
    }


    /**
     * Returns any extra path information after the servlet name but before the query string,
     * and translates it to a real path. If the URL does not have any extra path information, this method returns null.
     **/
    public String getPathTranslated() {
        return null;
    }


    /**
     * Checks whether the requested session ID came in as a cookie.
     **/
    public boolean isRequestedSessionIdFromCookie() {
        return _sessionID != null;
    }


    /**
     * Returns the login of the user making this request, if the user has been authenticated,
     * or null if the user has not been authenticated.
     * Whether the user name is sent with each subsequent request depends on the browser and type of authentication.
     **/
    public String getRemoteUser() {
        return _userName;
    }


    /**
     * Returns the session ID specified by the client. This may not be the same as the ID of the actual session in use.
     * For example, if the request specified an old (expired) session ID and the server has started a new session,
     * this method gets a new session with a new ID. If the request did not specify a session ID, this method returns null.
     **/
    public String getRequestedSessionId() {
        return _sessionID;
    }


    /**
     * Returns the part of this request's URL from the protocol name up to the query string in the first line of the HTTP request.
     **/
    public String getRequestURI() {
        return _requestContext.getRequestURI();
    }


    /**
     * Returns the current HttpSession associated with this request or, if there is no current session
     * and create is true, returns a new session.
     * <br>
     * If create is false and the request has no valid HttpSession, this method returns null.
     **/
    public HttpSession getSession( boolean create ) {
        _session = _context.getValidSession( getRequestedSessionId(), _session, create );
        return _session;
    }


    /**
     * Returns the current session associated with this request, or if the request does not have a session, creates one.
     **/
    public HttpSession getSession() {
        return getSession( true );
    }


    /**
     * Checks whether the requested session ID is still valid.
     **/
    public boolean isRequestedSessionIdValid() {
        return false;
    }


    /**
     * Checks whether the requested session ID came in as part of the request URL.
     **/
    public boolean isRequestedSessionIdFromURL() {
        return false;
    }


    /**
     * @deprecated use #isRequestedSessionIdFromURL
     **/
    public boolean isRequestedSessionIdFromUrl() {
        return isRequestedSessionIdFromURL();
    }


//--------------------------------- ServletRequest methods ----------------------------------------------------


    /**
     * Returns the length, in bytes, of the content contained in the
     * request and sent by way of the input stream or -1 if the
     * length is not known.
     **/
    public int getContentLength() {
        return getIntHeader( "Content-length" );
    }


    /**
     *
     * Returns the value of the named attribute as an <code>Object</code>.
     * This method allows the servlet engine to give the servlet
     * custom information about a request. This method returns
     * <code>null</code> if no attribute of the given name exists.
     **/
    public Object getAttribute( String name ) {
        return _attributes.get( name );
    }


    /**
     * Returns an <code>Enumeration</code> containing the
     * names of the attributes available to this request.
     * This method returns an empty <code>Enumeration</code>
     * if the request has no attributes available to it.
     **/
    public Enumeration getAttributeNames() {
        return _attributes.keys();
    }


    /**
     * Retrieves binary data from the body of the request as
     * a {@link ServletInputStream}, which
     * gives you the ability to read one line at a time.
     *
     * @return					a {@link ServletInputStream} object containing
     * 						the body of the request
     *
     * @exception IllegalStateException   	if the {@link #getReader} method
     * 						has already been called for this request
     *
     * @exception IOException    		if an input or output exception occurred
     *
     */
    public ServletInputStream getInputStream() throws IOException {
      if (_gotReader) {
        throw new IllegalStateException("getReader() has already been called for this request");
      }
      initializeInputStream();
      _gotInputStream = true;
      return _inputStream;
    }

    /**
     * initialize the inputStream
     */
    private void initializeInputStream() {
   	  if (_inputStream == null) {
         _inputStream = new ServletInputStreamImpl( _messageBody );
      }
    }

    /**
     * Returns the name of the character encoding style used in this
     * request. This method returns <code>null</code> if the request
     * does not use character encoding.
     **/
    public String getCharacterEncoding() {
        return _charset;
    }


    /**
     *
     * Returns an <code>Enumeration</code> of <code>String</code>
     * objects containing the names of the parameters contained
     * in this request. If the request has
     * no parameters or if the input stream is empty, returns an
     * empty <code>Enumeration</code>. The input stream is empty
     * when all the data returned by {@link #getInputStream} has
     * been read.
     **/
    public Enumeration getParameterNames() {
        return _requestContext.getParameterNames();
    }


    /**
     * Returns the MIME type of the content of the request, or
     * <code>null</code> if the type is not known. Same as the value
     * of the CGI variable CONTENT_TYPE.
     **/
    public String getContentType() {
    	String result;
    	result=_contentType;
    	/**
    	 * suggested fix by bug report 
    	 * [ 1509117 ] getContentType()
    	 * by Tom Parker     
    	 */
    	result=this.getHeader( "Content-Type" );
      return result;
    }


    /**
     * Returns the value of a request parameter as a <code>String</code>,
     * or <code>null</code> if the parameter does not exist. Request parameters
     * are extra information sent with the request.
     **/
    public String getParameter( String name ) {
        String[] parameters = getParameterValues( name );
        return parameters == null ? null : parameters[0];
    }


    /**
     * Returns an array of <code>String</code> objects containing
     * all of the values the
     * given request parameter has, or <code>null</code> if the
     * parameter does not exist. For example, in an HTTP servlet,
     * this method returns an array of <code>String</code> objects
     * containing the values of a query string or posted form.
     **/
    public String[] getParameterValues( String name ) {
        return _requestContext.getParameterValues( name );
    }


    /**
     * Returns the name and version of the protocol the request uses
     * in the form <i>protocol/majorVersion.minorVersion</i>, for
     * example, HTTP/1.1.
     **/
    public String getProtocol() {
        return "HTTP/1.1";
    }


    /**
     * Returns the name of the scheme used to make this request,
     * for example,
     * <code>http</code>, <code>https</code>, or <code>ftp</code>.
     * Different schemes have different rules for constructing URLs,
     * as noted in RFC 1738.
     **/
    public String getScheme() {
    	String result= isSecure() ? "https" : "http";
    	result=_protocol;
    	return result;
    }


    /**
     * Returns the fully qualified name of the client that sent the
     * request.
     **/
    public String getRemoteHost() {
        return "localhost";
    }


    /**
     * Returns the host name of the server that received the request.
     **/
    public String getServerName() {
        return "localhost";
    }


    /**
     * Returns the port number on which this request was received.
     **/
    public int getServerPort() {
        return 0;
    }


    /**
     *
     * @deprecated 	As of Version 2.1 of the Java Servlet API,
     * 			use {@link javax.servlet.ServletContext#getRealPath} instead.
     *
     */
    public String getRealPath( String path ) {
        throwNotImplementedYet();
        return "";
    }


    /**
     * Returns the body of the request as a <code>BufferedReader</code>
     * that translates character set encodings.
     * @since [ 1221537 ] Patch: ServletUnitHttpRequest.getReader not implemented yet
     * @author Tim  - timmorrow (SourceForge)
     * @return the reader
     **/
    public BufferedReader getReader() throws IOException {
    	if (_gotInputStream) {
    	     throw new IllegalStateException("getInputStream() has already been called on this request");
    	}
    	if (_reader == null) {
    	  initializeInputStream();
    	  String encoding = getCharacterEncoding();
    	  if (encoding == null) {
    	    encoding = HttpUnitUtils.DEFAULT_CHARACTER_SET;
    	  }
    	  _reader = new BufferedReader(new InputStreamReader(_inputStream, encoding));
    	  _gotReader = true;
    	}
    	return _reader;
    }


    /**
     * Returns the Internet Protocol (IP) address of the client
     * that sent the request.
     **/
    public String getRemoteAddr() {
        return LOOPBACK_ADDRESS;
    }


    /**
     *
     * Stores an attribute in the context of this request.
     * Attributes are reset between requests.
     **/
    public void setAttribute( String key, Object o ) {
        if (o == null) _attributes.remove( key );
        else _attributes.put( key, o );
    }


//--------------------------------- methods added to ServletRequest in Servlet API 2.2 ------------------------------------------------


    /**
     * Returns a boolean indicating whether this request was made using a secure channel, such as HTTPS.
     **/
    public boolean isSecure() {
        return _secure;
    }


    /**
     * Returns the preferred Locale that the client will accept content in, based on the Accept-Language header.
     * If the client request doesn't provide an Accept-Language header, this method returns the default locale for the server.
     **/
    public Locale getLocale() {
        return (Locale) getPreferredLocales().firstElement();
    }


    /**
     * Returns an Enumeration of Locale objects indicating, in decreasing order starting with the preferred locale,
     * the locales that are acceptable to the client based on the Accept-Language header.
     * If the client request doesn't provide an Accept-Language header, this
     * method returns an Enumeration containing one Locale, the default locale for the server.
     **/
    public java.util.Enumeration getLocales() {
        return getPreferredLocales().elements();
    }


    /**
     * Parses the accept-language header to obtain a vector of preferred locales
     * @return the preferred locales, sorted by qvalue
     */
    private Vector getPreferredLocales() {
        if (_locales == null) {
            _locales = new Vector();
            String languages = getHeader( "accept-language" );
            if (languages == null) {
                _locales.add( Locale.getDefault() );
            } else {
                 StringTokenizer st = new StringTokenizer( languages, "," );
                 ArrayList al = new ArrayList();
                 while (st.hasMoreTokens()) {
                     String token = st.nextToken();
                     al.add( new PrioritizedLocale( token ) );
                 }
                 Collections.sort( al );
                 for (Iterator iterator = al.iterator(); iterator.hasNext();) {
                     _locales.add( ((PrioritizedLocale) iterator.next()).getLocale() );
                 }
            }
         }
        return _locales;
    }


    /**
     * Removes an attribute from this request. This method is not generally needed
     * as attributes only persist as long as the request is being handled.
     **/
    public void removeAttribute( String name ) {
        _attributes.remove( name );
    }


    /**
     * Returns a RequestDispatcher object that acts as a wrapper for the resource located at the given path.
     * A RequestDispatcher object can be used to forward a request to the resource or to include the
     * resource in a response. The resource can be dynamic or static.
     *
     * The pathname specified may be relative, although it cannot extend outside the current servlet
     * context. If the path begins with a "/" it is interpreted as relative to the current context root.
     * This method returns null if the servlet container cannot return a RequestDispatcher.
     *
     * The difference between this method and ServletContext.getRequestDispatcher(java.lang.String)
     * is that this method can take a relative path.
     **/
    public RequestDispatcher getRequestDispatcher( String path ) {
        try {
            if (!path.startsWith( "/" )) path = combinedPath( getServletPath(), path );
            return _servletRequest.getServlet().getServletConfig().getServletContext().getRequestDispatcher( path );
        } catch (ServletException e) {
            return null;
        }
    }


    private String combinedPath( String basePath, String relativePath ) {
        if (basePath.indexOf( '/' ) < 0) return relativePath;
        return basePath.substring( 0, basePath.lastIndexOf( '/' ) ) + '/' + relativePath;
    }



//--------------------------------- methods added to HttpServletRequest in Servlet API 2.2 ------------------------------------------------


    /**
     * Returns a java.security.Principal object containing the name of the current authenticated user.
     * If the user has not been authenticated, the method returns null.
     **/
    public java.security.Principal getUserPrincipal() {
        return null;
    }


    /**
     * Returns a boolean indicating whether the authenticated user is included in the specified
     * logical "role". Roles and role membership can be defined using deployment descriptors.
     * If the user has not been authenticated, the method returns false.
     **/
    public boolean isUserInRole( String role ) {
        if (_roles == null) return false;
        for (int i = 0; i < _roles.length; i++) {
            if (role.equals( _roles[i] )) return true;
        }
        return false;
    }


    /**
     * Returns all the values of the specified request header as an Enumeration of String objects.
     **/
    public java.util.Enumeration getHeaders( String name ) {
        Vector list = new Vector();
        if (_headers.containsKey( name )) list.add( _headers.get( name ));
        return list.elements();
    }


    /**
     * Returns the portion of the request URI that indicates the context of the request.
     * The context path always comes first in a request URI. The path starts with a "/" character
     * but does not end with a "/" character. For servlets in the default (root) context,
     * this method returns "".
     **/
    public String getContextPath() {
        return _context.getContextPath();
    }


//--------------------------------------- methods added to ServletRequest in Servlet API 2.3 ----------------------------

    /**
     * Returns a java.util.Map of the parameters of this request.
     * Request parameters are extra information sent with the request. For HTTP servlets, parameters are contained
     * in the query string or posted form data.
     *
     * @since 1.3
     **/
    public Map getParameterMap() {
        return _requestContext.getParameterMap();
    }


    /**
     * Overrides the name of the character encoding used in the body of this request.
     * This method must be called prior to reading request parameters or reading input using getReader().
     *
     * @since 1.3
     **/
    public void setCharacterEncoding( String charset ) throws UnsupportedEncodingException {
        _charset = charset;
        _requestContext.setMessageEncoding( charset );
    }


//--------------------------------------- methods added to HttpServletRequest in Servlet API 2.3 ----------------------------


    /**
     * Reconstructs the URL the client used to make the request.
     * The returned URL contains a protocol, server name, port number, and server path, but
     * it does not include query string parameters.
     *
     * Because this method returns a StringBuffer, not a string, you can modify the URL easily, for example,
     * to append query parameters.
     *
     * This method is useful for creating redirect messages and for reporting errors.
     *
     * @since 1.3
     */
    public StringBuffer getRequestURL() {
        StringBuffer url = new StringBuffer();
        try {
            url.append( _request.getURL().getProtocol() ).append( "://" );
            url.append( _request.getURL().getHost() );
            url.append( _request.getURL().getPath() );
        } catch (MalformedURLException e) {
            throw new RuntimeException( "unable to read URL from request: " + _request );
        }
        return url;
    }


//--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ----------------------------

    public int getRemotePort() {
        return 0;  //To change body of implemented methods use File | Settings | File Templates.
    }

    public String getLocalName() {
        return "localhost";
    }

    public String getLocalAddr() {
        return "127.0.0.1";
    }

    public int getLocalPort() {
        return 0;  //To change body of implemented methods use File | Settings | File Templates.
    }

//--------------------------------------- methods added to ServletRequest in Servlet API 3.0 ----------------------------

    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
        return false;
    }

    public void login(String username, String password) throws ServletException {

    }

    public void logout() throws ServletException {

    }

    public Collection<Part> getParts() throws IOException, ServletException {
        return null;
    }

    public Part getPart(String name) throws IOException, ServletException {
        return null;
    }

    public ServletContext getServletContext() {
        return null;
    }

    public AsyncContext startAsync() throws IllegalStateException {
        return null;
    }

    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
        return null;
    }

    public boolean isAsyncStarted() {
        return false;
    }

    public boolean isAsyncSupported() {
        return false;
    }

    public AsyncContext getAsyncContext() {
        return null;
    }

    public DispatcherType getDispatcherType() {
        return null;
    }

//--------------------------------------- methods added to ServletRequest in Servlet API 3.1 ----------------------------

    public String changeSessionId() {
        return null;
    }

    public <T extends javax.servlet.http.HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
        return null;
    }

    public long getContentLengthLong() {
        return Long.parseLong(getHeader("Content-Length"));
    }

//--------------------------------------------- package members ----------------------------------------------


    private void addCookie( Cookie cookie ) {
        _cookies.addElement( cookie );
        if (cookie.getName().equalsIgnoreCase( ServletUnitHttpSession.SESSION_COOKIE_NAME )) {
            _sessionID = cookie.getValue();
        }
    }


    private ServletUnitHttpSession getServletSession() {
        return (ServletUnitHttpSession) getSession();
    }


    void readFormAuthentication() {
        if (getSession( /* create */ false ) != null) {
            recordAuthenticationInfo( getServletSession().getUserName(), getServletSession().getRoles() );
        }
    }


    void readBasicAuthentication() {
        String authorizationHeader = (String) _headers.get( "Authorization" );

        if (authorizationHeader != null) {
            String userAndPassword = Base64.decode( authorizationHeader.substring( authorizationHeader.indexOf( ' ' ) + 1 ) );
            int colonPos = userAndPassword.indexOf( ':' );
            recordAuthenticationInfo( userAndPassword.substring( 0, colonPos ),
                                      toArray( userAndPassword.substring( colonPos+1 ) ) );
        }
    }

    static String[] toArray( String roleList ) {
        StringTokenizer st = new StringTokenizer( roleList, "," );
        String[] result = new String[ st.countTokens() ];
        for (int i = 0; i < result.length; i++) {
            result[i] = st.nextToken();
        }
        return result;
    }


    void recordAuthenticationInfo( String userName, String[] roles ) {
        _userName = userName;
        _roles    = roles;
    }


//--------------------------------------------- private members ----------------------------------------------

    final static private String LOOPBACK_ADDRESS = "127.0.0.1";

    private WebRequest                 _request;
    private ServletMetaData            _servletRequest;
    private WebClient.HeaderDictionary _headers;
    private ServletUnitContext         _context;
    private ServletUnitHttpSession     _session;
    private Hashtable                  _attributes = new Hashtable();
    private Vector                     _cookies    = new Vector();
    private String                     _sessionID;
    private byte[]                     _messageBody;

    private String                     _userName;
    private String[]                   _roles;


    private void throwNotImplementedYet() {
        throw new RuntimeException( "Not implemented yet" );
    }


    private void setCookiesFromHeader( Dictionary clientHeaders ) {
        String cookieHeader = (String) clientHeaders.get( "Cookie" );
        if (cookieHeader == null) return;

        StringTokenizer st = new StringTokenizer( cookieHeader, ",;=", true );
        String lastToken = st.nextToken();
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            if (token.equals( "=" )) {
                if (st.hasMoreTokens()) addCookie( new Cookie( lastToken.trim(), st.nextToken().trim() ) );
            }
            lastToken = token;
        }
    }


    static class PrioritizedLocale implements Comparable {

        private Locale _locale;
        private float  _priority;


        PrioritizedLocale( String languageSpec ) {
            int semiIndex = languageSpec.indexOf( ';' );
            if (semiIndex < 0) {
                _priority = 1;
                _locale = parseLocale( languageSpec );
            } else {
                _priority = Float.parseFloat( languageSpec.substring( languageSpec.indexOf( '=', semiIndex )+1 ) );
                _locale = parseLocale( languageSpec.substring( 0, semiIndex ) );
            }
        }


        private Locale parseLocale( String range ) {
            range = range.trim();
            int dashIndex = range.indexOf( '-' );
            if (dashIndex < 0) {
                return new Locale( range, "" );
            } else {
                return new Locale( range.substring( 0, dashIndex ), range.substring( dashIndex+1 ) );
            }
        }


        public Locale getLocale() {
            return _locale;
        }


        public int compareTo( Object o ) {
            if (!(o instanceof PrioritizedLocale)) throw new IllegalArgumentException( "may only combine with other prioritized locales" );
            PrioritizedLocale other = (PrioritizedLocale) o;
            return _priority == other._priority ? _locale.getLanguage().compareTo( other._locale.getLanguage() )
                                                : (_priority < other._priority ? +1 : -1 );
        }

    }
}