001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.http.mock;
018
019import java.io.BufferedReader;
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.FileInputStream;
023import java.io.IOException;
024import java.io.InputStreamReader;
025import java.io.OutputStream;
026import java.io.UnsupportedEncodingException;
027import java.nio.charset.Charset;
028import java.security.Principal;
029import java.text.DateFormat;
030import java.text.ParseException;
031import java.util.ArrayList;
032import java.util.Arrays;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Date;
036import java.util.Enumeration;
037import java.util.HashMap;
038import java.util.Iterator;
039import java.util.LinkedHashMap;
040import java.util.List;
041import java.util.Locale;
042import java.util.Map;
043
044import javax.servlet.AsyncContext;
045import javax.servlet.DispatcherType;
046import javax.servlet.ReadListener;
047import javax.servlet.RequestDispatcher;
048import javax.servlet.ServletContext;
049import javax.servlet.ServletException;
050import javax.servlet.ServletInputStream;
051import javax.servlet.ServletRequest;
052import javax.servlet.ServletResponse;
053import javax.servlet.http.Cookie;
054import javax.servlet.http.HttpServletRequest;
055import javax.servlet.http.HttpServletResponse;
056import javax.servlet.http.HttpSession;
057import javax.servlet.http.HttpUpgradeHandler;
058import javax.servlet.http.Part;
059
060import org.apache.commons.fileupload.FileUploadBase;
061import org.apache.wicket.Application;
062import org.apache.wicket.WicketRuntimeException;
063import org.apache.wicket.mock.MockRequestParameters;
064import org.apache.wicket.request.Url;
065import org.apache.wicket.request.Url.QueryParameter;
066import org.apache.wicket.util.encoding.UrlDecoder;
067import org.apache.wicket.util.encoding.UrlEncoder;
068import org.apache.wicket.util.file.File;
069import org.apache.wicket.util.io.IOUtils;
070import org.apache.wicket.util.string.StringValue;
071import org.apache.wicket.util.string.Strings;
072import org.apache.wicket.util.value.ValueMap;
073
074
075/**
076 * Mock servlet request. Implements all of the methods from the standard HttpServletRequest class
077 * plus helper methods to aid setting up a request.
078 * 
079 * @author Chris Turner
080 */
081public class MockHttpServletRequest implements HttpServletRequest
082{
083        /**
084         * A holder class for an uploaded file.
085         * 
086         * @author Frank Bille (billen)
087         */
088        private static class UploadedFile
089        {
090                private File file;
091                private String contentType;
092
093                /**
094                 * Construct.
095                 * 
096                 * @param fieldName
097                 * @param file
098                 * @param contentType
099                 */
100                public UploadedFile(String fieldName, File file, String contentType)
101                {
102                        this.file = file;
103                        this.contentType = contentType;
104                }
105
106                /**
107                 * @return The content type of the file. Mime type.
108                 */
109                public String getContentType()
110                {
111                        return contentType;
112                }
113
114                /**
115                 * @return The uploaded file.
116                 */
117                public File getFile()
118                {
119                        return file;
120                }
121        }
122
123        private final ValueMap attributes = new ValueMap();
124
125        private String authType;
126
127        private String characterEncoding = "UTF-8";
128
129        private final ServletContext context;
130
131        private final Map<Cookies.Key, Cookie> cookies = new LinkedHashMap<>();
132
133        private final ValueMap headers = new ValueMap();
134
135        private String method;
136
137        private final LinkedHashMap<String, String[]> parameters = new LinkedHashMap<>();
138
139        private final LinkedHashMap<String, Part> parts = new LinkedHashMap<>();
140
141        private String path;
142
143        private final HttpSession session;
144
145        private String url;
146
147        private Map<String, List<UploadedFile>> uploadedFiles;
148
149        private boolean useMultiPartContentType;
150
151        private boolean secure = false;
152
153        private String remoteAddr = "127.0.0.1";
154
155        private String scheme = "http";
156
157        private String serverName = "localhost";
158
159        private int serverPort = 80;
160
161        /**
162         * Create the request using the supplied session object. Note that in order for temporary
163         * sessions to work, the supplied session must be an instance of {@link MockHttpSession}
164         * 
165         * @param application
166         *            The application that this request is for
167         * @param session
168         *            The session object
169         * @param context
170         *            The current servlet context
171         * @param locale
172         *            The current locale                          
173         */
174        public MockHttpServletRequest(final Application application, final HttpSession session,
175                final ServletContext context, Locale locale)
176        {
177                this.session = session;
178                this.context = context;
179                initialize(locale);
180        }
181        
182        public MockHttpServletRequest(final Application application, final HttpSession session,
183                        final ServletContext context) 
184        {
185                this(application, session, context, Locale.getDefault());
186        }
187
188        /**
189         * Add a new cookie.
190         * 
191         * @param cookie
192         *            The cookie
193         */
194        public void addCookie(final Cookie cookie)
195        {
196                cookies.put(Cookies.keyOf(cookie), cookie);
197        }
198
199        /**
200         * @param cookies
201         */
202        public void addCookies(Iterable<Cookie> cookies)
203        {
204                for (Cookie cookie : cookies)
205                {
206                        addCookie(cookie);
207                }
208        }
209
210        /**
211         * Add an uploaded file to the request. Use this to simulate a file that has been uploaded to a
212         * field.
213         * 
214         * @param fieldName
215         *            The fieldname of the upload field.
216         * @param file
217         *            The file to upload.
218         * @param contentType
219         *            The content type of the file. Must be a correct mimetype.
220         */
221        public void addFile(String fieldName, File file, String contentType)
222        {
223                if (file != null) {
224                        if (file.exists() == false)
225                        {
226                                throw new IllegalArgumentException(
227                                        "File does not exists. You must provide an existing file: "
228                                                + file.getAbsolutePath());
229                        }
230
231                        if (file.isFile() == false)
232                        {
233                                throw new IllegalArgumentException(
234                                        "You can only add a File, which is not a directory. Only files can be uploaded.");
235                        }
236                }
237
238                if (uploadedFiles == null)
239                {
240                        uploadedFiles = new HashMap<>();
241                }
242
243                UploadedFile uf = new UploadedFile(fieldName, file, contentType);
244
245                List<UploadedFile> filesPerField = uploadedFiles.get(fieldName);
246                if (filesPerField == null)
247                {
248                        filesPerField = new ArrayList<>();
249                        uploadedFiles.put(fieldName, filesPerField);
250                }
251
252                filesPerField.add(uf);
253                setUseMultiPartContentType(true);
254        }
255
256        /**
257         * Add a header to the request.
258         * 
259         * @param name
260         *            The name of the header to add
261         * @param value
262         *            The value
263         */
264        public void addHeader(String name, String value)
265        {
266                @SuppressWarnings("unchecked")
267                List<String> list = (List<String>)headers.get(name);
268                if (list == null)
269                {
270                        list = new ArrayList<>(1);
271                        headers.put(name, list);
272                }
273                list.add(value);
274        }
275
276        /**
277         * Sets a header to the request. Overrides any previous value of this header.
278         * 
279         * @param name
280         *            The name of the header to add
281         * @param value
282         *            The value
283         * @see #addHeader(String, String)
284         */
285        public void setHeader(String name, String value)
286        {
287                @SuppressWarnings("unchecked")
288                List<String> list = (List<String>)headers.get(name);
289                if (list == null)
290                {
291                        list = new ArrayList<>(1);
292                        headers.put(name, list);
293                }
294                list.clear();
295                list.add(value);
296        }
297
298        /**
299         * @param name
300         * @param date
301         */
302        public void addDateHeader(String name, long date)
303        {
304                DateFormat df = DateFormat.getDateInstance(DateFormat.FULL);
305                String dateString = df.format(new Date(date));
306                addHeader(name, dateString);
307        }
308
309        /**
310         * Get an attribute.
311         * 
312         * @param name
313         *            The attribute name
314         * @return The value, or null
315         */
316        @Override
317        public Object getAttribute(final String name)
318        {
319                return attributes.get(name);
320        }
321
322        /**
323         * Get the names of all of the values.
324         * 
325         * @return The names
326         */
327        @Override
328        public Enumeration<String> getAttributeNames()
329        {
330                return Collections.enumeration(attributes.keySet());
331        }
332
333        // HttpServletRequest methods
334
335        /**
336         * Get the auth type.
337         * 
338         * @return The auth type
339         */
340        @Override
341        public String getAuthType()
342        {
343                return authType;
344        }
345
346        /**
347         * Get the current character encoding.
348         * 
349         * @return The character encoding
350         */
351        @Override
352        public String getCharacterEncoding()
353        {
354                return characterEncoding;
355        }
356
357        /**
358         * Get the current character set.
359         * 
360         * @return The character set
361         */
362        public Charset getCharset()
363        {
364                return Charset.forName(characterEncoding);
365        }
366
367        /**
368         * true will force Request generate multiPart ContentType and ContentLength
369         * 
370         * @param useMultiPartContentType
371         */
372        public void setUseMultiPartContentType(boolean useMultiPartContentType)
373        {
374                this.useMultiPartContentType = useMultiPartContentType;
375        }
376
377
378        /**
379         * Return the length of the content. This is always -1 except if useMultiPartContentType set as
380         * true. Then the length will be the length of the generated request.
381         * 
382         * @return -1 if useMultiPartContentType is false. Else the length of the generated request.
383         */
384        @Override
385        public int getContentLength()
386        {
387                if (useMultiPartContentType)
388                {
389                        byte[] request = buildRequest();
390                        return request.length;
391                }
392
393                return -1;
394        }
395
396        @Override
397        public long getContentLengthLong()
398        {
399                return getContentLength();
400        }
401
402        /**
403         * If useMultiPartContentType set as true return the correct content-type.
404         * 
405         * @return The correct multipart content-type if useMultiPartContentType is true. Else null.
406         */
407        @Override
408        public String getContentType()
409        {
410                if (useMultiPartContentType)
411                {
412                        return FileUploadBase.MULTIPART_FORM_DATA + "; boundary=abcdefgABCDEFG";
413                }
414
415                return null;
416        }
417
418        /**
419         * Get the context path. For this mock implementation the name of the application is always
420         * returned.
421         * 
422         * @return The context path
423         */
424        @Override
425        public String getContextPath()
426        {
427                // return "/" + application.getName();
428                return "/context";
429        }
430
431        /**
432         * @param name
433         * @return Cookie
434         */
435        public Cookie getCookie(String name)
436        {
437                Cookie[] cookies = getCookies();
438                if (cookies == null)
439                {
440                        return null;
441                }
442                for (Cookie cookie : cookies)
443                {
444                        if (cookie.getName().equals(name))
445                        {
446                                return Cookies.copyOf(cookie);
447                        }
448                }
449                return null;
450        }
451
452        /**
453         * Get all of the cookies for this request.
454         * 
455         * @return The cookies
456         */
457        @Override
458        public Cookie[] getCookies()
459        {
460                if (cookies.isEmpty())
461                {
462                        return null;
463                }
464                List<Cookie> cookieValues = new ArrayList<Cookie>();
465                cookieValues.addAll(cookies.values());
466                return cookieValues.toArray(new Cookie[cookieValues.size()]);
467        }
468
469        /**
470         * Get the given header as a date.
471         * 
472         * @param name
473         *            The header name
474         * @return The date, or -1 if header not found
475         * @throws IllegalArgumentException
476         *             If the header cannot be converted
477         */
478        @Override
479        public long getDateHeader(final String name) throws IllegalArgumentException
480        {
481                String value = getHeader(name);
482                if (value == null)
483                {
484                        return -1;
485                }
486
487                DateFormat df = DateFormat.getDateInstance(DateFormat.FULL);
488                try
489                {
490                        return df.parse(value).getTime();
491                }
492                catch (ParseException e)
493                {
494                        throw new IllegalArgumentException("Can't convert header to date " + name + ": "
495                                + value);
496                }
497        }
498
499        /**
500         * Get the given header value.
501         * 
502         * @param name
503         *            The header name
504         * @return The header value or null
505         */
506        @Override
507        public String getHeader(final String name)
508        {
509                @SuppressWarnings("unchecked")
510                final List<String> l = (List<String>)headers.get(name);
511                if (l == null || l.size() < 1)
512                {
513                        return null;
514                }
515                else
516                {
517                        return l.get(0);
518                }
519        }
520
521        /**
522         * Get the names of all of the headers.
523         * 
524         * @return The header names
525         */
526        @Override
527        public Enumeration<String> getHeaderNames()
528        {
529                return Collections.enumeration(headers.keySet());
530        }
531
532        /**
533         * Get enumeration of all header values with the given name.
534         * 
535         * @param name
536         *            The name
537         * @return The header values
538         */
539        @Override
540        public Enumeration<String> getHeaders(final String name)
541        {
542                @SuppressWarnings("unchecked")
543                List<String> list = (List<String>)headers.get(name);
544                if (list == null)
545                {
546                        list = new ArrayList<String>();
547                }
548                return Collections.enumeration(list);
549        }
550
551        /**
552         * Returns an input stream if there has been added some uploaded files. Use
553         * {@link #addFile(String, File, String)} to add some uploaded files.
554         * 
555         * @return The input stream
556         * @throws IOException
557         *             If an I/O related problem occurs
558         */
559        @Override
560        public ServletInputStream getInputStream() throws IOException
561        {
562                byte[] request = buildRequest();
563
564                // Ok lets make an input stream to return
565                final ByteArrayInputStream bais = new ByteArrayInputStream(request);
566
567                return new ServletInputStream()
568                {
569                        private boolean isFinished = false;
570                        private boolean isReady = true;
571
572                        @Override
573                        public boolean isFinished()
574                        {
575                                return isFinished;
576                        }
577
578                        @Override
579                        public boolean isReady()
580                        {
581                                return isReady;
582                        }
583
584                        @Override
585                        public void setReadListener(ReadListener readListener)
586                        {
587                        }
588
589                        @Override
590                        public int read()
591                        {
592                                isFinished = true;
593                                isReady = false;
594                                return bais.read();
595                        }
596                };
597        }
598
599        /**
600         * Get the given header as an int.
601         * 
602         * @param name
603         *            The header name
604         * @return The header value or -1 if header not found
605         * @throws NumberFormatException
606         *             If the header is not formatted correctly
607         */
608        @Override
609        public int getIntHeader(final String name)
610        {
611                String value = getHeader(name);
612                if (value == null)
613                {
614                        return -1;
615                }
616                return Integer.valueOf(value);
617        }
618
619        /**
620         * Get the locale of the request. Attempts to decode the Accept-Language header and if not found
621         * returns the default locale of the JVM.
622         * 
623         * @return The locale
624         */
625        @Override
626        public Locale getLocale()
627        {
628                return getLocales().nextElement();
629        }
630
631        public void setLocale(Locale locale) {
632                setHeader("Accept-Language", locale.getLanguage() + '-' + locale.getCountry());
633        }
634
635        /**
636         * 
637         * @param value
638         * @return locale
639         */
640        private Locale getLocale(final String value)
641        {
642                final String[] bits = Strings.split(value, '-');
643                if (bits.length < 1)
644                {
645                        return null;
646                }
647
648                final String language = bits[0].toLowerCase(Locale.ROOT);
649                if (bits.length > 1)
650                {
651                        final String country = bits[1].toUpperCase(Locale.ROOT);
652                        return new Locale(language, country);
653                }
654                else
655                {
656                        return new Locale(language);
657                }
658        }
659
660        /**
661         * Return all the accepted locales. This implementation always returns just one.
662         * 
663         * @return The locales
664         */
665        @Override
666        public Enumeration<Locale> getLocales()
667        {
668                List<Locale> list = new ArrayList<>();
669                String header = getHeader("Accept-Language");
670                if (header != null)
671                {
672                        int idxOfSemicolon = header.indexOf(';');
673                        if (idxOfSemicolon > -1) {
674                                header = header.substring(0 , idxOfSemicolon);
675                        }
676                        final String[] locales = Strings.split(header, ',');
677                        for (String value : locales)
678                        {
679                                Locale locale = getLocale(value);
680                                if (locale != null)
681                                {
682                                        list.add(locale);
683                                }
684                        }
685                }
686
687                if (list.size() == 0)
688                {
689                        list.add(Locale.getDefault());
690                }
691
692                return Collections.enumeration(list);
693        }
694
695        /**
696         * Get the method.
697         * 
698         * @return The method
699         */
700        @Override
701        public String getMethod()
702        {
703                return method;
704        }
705
706        /**
707         * Get the request parameter with the given name.
708         * 
709         * @param name
710         *            The parameter name
711         * @return The parameter value, or null
712         */
713        @Override
714        public String getParameter(final String name)
715        {
716                String[] param = getParameterMap().get(name);
717                if (param == null)
718                {
719                        return null;
720                }
721                else
722                {
723                        return param[0];
724                }
725        }
726
727        /**
728         * Get the map of all of the parameters.
729         * 
730         * @return The parameters
731         */
732        @Override
733        public Map<String, String[]> getParameterMap()
734        {
735                Map<String, String[]> params = new HashMap<>(parameters);
736
737                for (String name : post.getParameterNames())
738                {
739                        List<StringValue> values = post.getParameterValues(name);
740                        for (StringValue value : values)
741                        {
742                                String[] present = params.get(name);
743                                if (present == null)
744                                {
745                                        params.put(name, new String[] { value.toString() });
746                                }
747                                else
748                                {
749                                        String[] newval = new String[present.length + 1];
750                                        System.arraycopy(present, 0, newval, 0, present.length);
751                                        newval[newval.length - 1] = value.toString();
752                                        params.put(name, newval);
753                                }
754                        }
755                }
756
757                return params;
758        }
759
760        /**
761         * Get the names of all of the parameters.
762         * 
763         * @return The parameter names
764         */
765        @Override
766        public Enumeration<String> getParameterNames()
767        {
768                return Collections.enumeration(getParameterMap().keySet());
769        }
770
771        /**
772         * Get the values for the given parameter.
773         * 
774         * @param name
775         *            The name of the parameter
776         * @return The return values
777         */
778        @Override
779        public String[] getParameterValues(final String name)
780        {
781                Object value = getParameterMap().get(name);
782                if (value == null)
783                {
784                        return new String[0];
785                }
786
787                if (value instanceof String[])
788                {
789                        return (String[])value;
790                }
791                else
792                {
793                        String[] result = new String[1];
794                        result[0] = value.toString();
795                        return result;
796                }
797        }
798
799        /**
800         * Get the path info.
801         * 
802         * @return The path info
803         */
804        @Override
805        public String getPathInfo()
806        {
807                return path;
808        }
809
810        /**
811         * Always returns null.
812         * 
813         * @return null
814         */
815        @Override
816        public String getPathTranslated()
817        {
818                return null;
819        }
820
821        /**
822         * Get the protocol.
823         * 
824         * @return Always HTTP/1.1
825         */
826        @Override
827        public String getProtocol()
828        {
829                return "HTTP/1.1";
830        }
831
832        /**
833         * Get the query string part of the request.
834         * 
835         * @return The query string
836         */
837        @Override
838        public String getQueryString()
839        {
840                if (parameters.size() == 0)
841                {
842                        return null;
843                }
844                else
845                {
846                        final StringBuilder buf = new StringBuilder();
847                        for (Iterator<String> iterator = parameters.keySet().iterator(); iterator.hasNext();)
848                        {
849                                final String name = iterator.next();
850                                final String[] values = getParameterValues(name);
851                                for (int i = 0; i < values.length; i++)
852                                {
853                                        if (name != null)
854                                        {
855                                                buf.append(UrlEncoder.QUERY_INSTANCE.encode(name, getCharset()));
856                                        }
857                                        buf.append('=');
858                                        if (values[i] != null)
859                                        {
860                                                buf.append(UrlEncoder.QUERY_INSTANCE.encode(values[i], getCharset()));
861                                        }
862                                        if (i + 1 < values.length)
863                                        {
864                                                buf.append('&');
865                                        }
866                                }
867                                if (iterator.hasNext())
868                                {
869                                        buf.append('&');
870                                }
871                        }
872                        return buf.toString();
873                }
874        }
875
876        /**
877         * This feature is not implemented at this time as we are not supporting binary servlet input.
878         * This functionality may be added in the future.
879         * 
880         * @return The reader
881         * @throws IOException
882         *             If an I/O related problem occurs
883         */
884        @Override
885        public BufferedReader getReader() throws IOException
886        {
887                return new BufferedReader(new InputStreamReader(getInputStream()));
888        }
889
890        /**
891         * Deprecated method - should not be used.
892         * 
893         * @param name
894         *            The name
895         * @return The path
896         * @deprecated Use ServletContext.getRealPath(String) instead.
897         */
898        @Override
899        @Deprecated
900        public String getRealPath(String name)
901        {
902                return context.getRealPath(name);
903        }
904
905        /**
906         * @return the remote address of the client
907         */
908        @Override
909        public String getRemoteAddr()
910        {
911                return remoteAddr;
912        }
913
914        /**
915         * 
916         * @param addr
917         *            Format: "aaa.bbb.ccc.ddd"
918         */
919        public void setRemoteAddr(String addr)
920        {
921                remoteAddr = addr;
922        }
923
924        /**
925         * Get the remote host.
926         * 
927         * @return Return 'localhost' by default
928         */
929        @Override
930        public String getRemoteHost()
931        {
932                return "localhost";
933        }
934
935        /**
936         * Get the name of the remote user from the REMOTE_USER header.
937         * 
938         * @return The name of the remote user
939         */
940        @Override
941        public String getRemoteUser()
942        {
943                return getHeader("REMOTE_USER");
944        }
945
946        /**
947         * Return a dummy dispatcher that just records that dispatch has occurred without actually doing
948         * anything.
949         * 
950         * @param name
951         *            The name to dispatch to
952         * @return The dispatcher
953         */
954        @Override
955        public RequestDispatcher getRequestDispatcher(String name)
956        {
957                return context.getRequestDispatcher(name);
958        }
959
960        /**
961         * Get the requested session id. Always returns the id of the current session.
962         * 
963         * @return The session id
964         */
965        @Override
966        public String getRequestedSessionId()
967        {
968                if (session instanceof MockHttpSession && ((MockHttpSession)session).isTemporary())
969                {
970                        return null;
971                }
972                return session.getId();
973        }
974
975        /**
976         * Returns context path and servlet path concatenated, typically
977         * /applicationClassName/applicationClassName
978         * 
979         * @return The path value
980         * @see javax.servlet.http.HttpServletRequest#getRequestURI()
981         */
982        @Override
983        public String getRequestURI()
984        {
985                if (url == null)
986                {
987                        return getContextPath() + getServletPath();
988                }
989                else
990                {
991                        int index = url.indexOf("?");
992                        if (index != -1)
993                        {
994                                return url.substring(0, index);
995                        }
996                }
997                return url;
998        }
999
1000        /**
1001         * Try to build a rough URL.
1002         * 
1003         * @return The url
1004         * @see javax.servlet.http.HttpServletRequest#getRequestURL()
1005         */
1006        @Override
1007        public StringBuffer getRequestURL()
1008        {
1009                StringBuffer buf = new StringBuffer();
1010                buf.append("http://localhost");
1011                buf.append(getContextPath());
1012                if (getPathInfo() != null)
1013                {
1014                        buf.append(getPathInfo());
1015                }
1016
1017                // No query parameter. See
1018                // http://download.oracle.com/javaee/5/api/javax/servlet/http/HttpServletRequest.html#getRequestURL()
1019                return buf;
1020        }
1021
1022        /**
1023         * Get the scheme.
1024         * 
1025         * @return the scheme of this request
1026         */
1027        @Override
1028        public String getScheme()
1029        {
1030                return scheme;
1031        }
1032
1033        /**
1034         * Sets the scheme of this request
1035         * <p/>
1036         * set the <code>secure</code> flag accordingly (<code>true</code> for 'https',
1037         * <code>false</code> otherwise)
1038         * 
1039         * @param scheme
1040         *            protocol scheme (e.g. https, http, ftp)
1041         * 
1042         * @see #isSecure()
1043         */
1044        public void setScheme(String scheme)
1045        {
1046                this.scheme = scheme;
1047                secure = "https".equalsIgnoreCase(scheme);
1048        }
1049
1050        /**
1051         * Get the server name.
1052         * 
1053         * @return by default returns 'localhost'
1054         */
1055        @Override
1056        public String getServerName()
1057        {
1058                return serverName;
1059        }
1060
1061        /**
1062         * Set the server name.
1063         * 
1064         * @param serverName
1065         *            content of 'Host' header
1066         */
1067        public void setServerName(final String serverName)
1068        {
1069                this.serverName = serverName;
1070        }
1071
1072        /**
1073         * @return the server port of this request
1074         */
1075        @Override
1076        public int getServerPort()
1077        {
1078                return serverPort;
1079        }
1080
1081        /**
1082         * Sets the server port for this request
1083         * 
1084         * @param port
1085         */
1086        public void setServerPort(int port)
1087        {
1088                serverPort = port;
1089        }
1090
1091        /**
1092         * The servlet path may either be the application name or /. For test purposes we always return
1093         * the servlet name.
1094         * 
1095         * @return The servlet path
1096         */
1097        @Override
1098        public String getServletPath()
1099        {
1100                return "/servlet";
1101        }
1102
1103        /**
1104         * Get the sessions.
1105         * 
1106         * @return The session
1107         */
1108        @Override
1109        public HttpSession getSession()
1110        {
1111                return getSession(true);
1112        }
1113
1114        @Override
1115        public String changeSessionId()
1116        {
1117                final HttpSession oldSession = getSession(false);
1118                if (oldSession == null)
1119                {
1120                        throw new IllegalStateException("There is no active session associated with the current request");
1121                }
1122                oldSession.invalidate();
1123
1124                final HttpSession newSession = getSession(true);
1125
1126                return newSession.getId();
1127        }
1128
1129        /**
1130         * Get the session.
1131         * 
1132         * @param createNew
1133         *            Ignored, there is always a session
1134         * @return The session
1135         */
1136        @Override
1137        public HttpSession getSession(boolean createNew)
1138        {
1139                HttpSession sess = null;
1140                if (session instanceof MockHttpSession)
1141                {
1142                        MockHttpSession mockHttpSession = (MockHttpSession)session;
1143                        if (createNew)
1144                        {
1145                                mockHttpSession.setTemporary(false);
1146                        }
1147
1148                        if (mockHttpSession.isTemporary() == false)
1149                        {
1150                                sess = session;
1151                        }
1152                }
1153                return sess;
1154        }
1155
1156        /**
1157         * Get the user principal.
1158         * 
1159         * @return A user principal
1160         */
1161        @Override
1162        public Principal getUserPrincipal()
1163        {
1164                final String user = getRemoteUser();
1165                if (user == null)
1166                {
1167                        return null;
1168                }
1169                else
1170                {
1171                        return new Principal()
1172                        {
1173                                @Override
1174                                public String getName()
1175                                {
1176                                        return user;
1177                                }
1178                        };
1179                }
1180        }
1181
1182        /**
1183         * @return True if there has been added files to this request using
1184         *         {@link #addFile(String, File, String)}
1185         */
1186        public boolean hasUploadedFiles()
1187        {
1188                return uploadedFiles != null;
1189        }
1190
1191        /**
1192         * Reset the request back to a default state.
1193         * @param locale 
1194         */
1195        public void initialize(Locale locale)
1196        {
1197                authType = null;
1198                method = "post";
1199                cookies.clear();
1200                setDefaultHeaders(locale);
1201                path = null;
1202                url = null;
1203                characterEncoding = "UTF-8";
1204                parameters.clear();
1205                attributes.clear();
1206                post.reset();
1207        }
1208
1209        /**
1210         * Check whether session id is from a cookie. Always returns true.
1211         * 
1212         * @return Always true
1213         */
1214        @Override
1215        public boolean isRequestedSessionIdFromCookie()
1216        {
1217                return true;
1218        }
1219
1220        /**
1221         * Check whether session id is from a url rewrite. Always returns false.
1222         * 
1223         * @return Always false
1224         */
1225        @Override
1226        public boolean isRequestedSessionIdFromUrl()
1227        {
1228                return false;
1229        }
1230
1231        @Override
1232        public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
1233        {
1234                return false;
1235        }
1236
1237        @Override
1238        public void login(String username, String password) throws ServletException
1239        {
1240        }
1241
1242        @Override
1243        public void logout() throws ServletException
1244        {
1245        }
1246
1247        @Override
1248        public Collection<Part> getParts() throws IOException, ServletException
1249        {
1250                return parts.values();
1251        }
1252
1253        @Override
1254        public Part getPart(String name) throws IOException, ServletException
1255        {
1256                return parts.get(name);
1257        }
1258
1259        @Override
1260        public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException
1261        {
1262                return null;
1263        }
1264
1265        public MockHttpServletRequest setPart(String name, Part part) {
1266                parts.put(name, part);
1267                return this;
1268        }
1269
1270        /**
1271         * Check whether session id is from a url rewrite. Always returns false.
1272         * 
1273         * @return Always false
1274         */
1275        @Override
1276        public boolean isRequestedSessionIdFromURL()
1277        {
1278                return false;
1279        }
1280
1281        /**
1282         * Check whether the session id is valid.
1283         * 
1284         * @return Always true
1285         */
1286        @Override
1287        public boolean isRequestedSessionIdValid()
1288        {
1289                return true;
1290        }
1291
1292        /**
1293         * @return <code>true</code> if this request's scheme is 'https', <code>false</code> - otherwise
1294         */
1295        @Override
1296        public boolean isSecure()
1297        {
1298                return secure;
1299        }
1300
1301        /**
1302         * 
1303         * @param secure
1304         */
1305        public void setSecure(boolean secure)
1306        {
1307                this.secure = secure;
1308        }
1309
1310        /**
1311         * NOT IMPLEMENTED.
1312         * 
1313         * @param name
1314         *            The role name
1315         * @return Always false
1316         */
1317        @Override
1318        public boolean isUserInRole(String name)
1319        {
1320                return false;
1321        }
1322
1323        /**
1324         * Remove the given attribute.
1325         * 
1326         * @param name
1327         *            The name of the attribute
1328         */
1329        @Override
1330        public void removeAttribute(final String name)
1331        {
1332                attributes.remove(name);
1333        }
1334
1335        /**
1336         * Set the given attribute.
1337         * 
1338         * @param name
1339         *            The attribute name
1340         * @param o
1341         *            The value to set
1342         */
1343        @Override
1344        public void setAttribute(final String name, final Object o)
1345        {
1346                attributes.put(name, o);
1347        }
1348
1349        /**
1350         * Set the auth type.
1351         * 
1352         * @param authType
1353         *            The auth type
1354         */
1355        public void setAuthType(final String authType)
1356        {
1357                this.authType = authType;
1358        }
1359
1360        /**
1361         * Set the character encoding.
1362         * 
1363         * @param encoding
1364         *            The character encoding
1365         * @throws UnsupportedEncodingException
1366         *             If encoding not supported
1367         */
1368        @Override
1369        public void setCharacterEncoding(final String encoding) throws UnsupportedEncodingException
1370        {
1371                characterEncoding = encoding;
1372        }
1373
1374        /**
1375         * Set the cookies.
1376         * 
1377         * @param theCookies
1378         *            The cookies
1379         */
1380        public void setCookies(final Cookie[] theCookies)
1381        {
1382                cookies.clear();
1383                addCookies(Arrays.asList(theCookies));
1384        }
1385
1386        /**
1387         * Set the method.
1388         * 
1389         * @param method
1390         *            The method
1391         */
1392        public void setMethod(final String method)
1393        {
1394                this.method = method;
1395        }
1396
1397        /**
1398         * Set a parameter.
1399         * 
1400         * @param name
1401         *            The name
1402         * @param value
1403         *            The value
1404         */
1405        public void setParameter(final String name, final String value)
1406        {
1407                if (value == null)
1408                {
1409                        parameters.remove(name);
1410                }
1411                else
1412                {
1413                        parameters.put(name, new String[] { value });
1414                }
1415        }
1416
1417        /**
1418         * @param name
1419         * @param value
1420         */
1421        public void addParameter(final String name, final String value)
1422        {
1423                if (value == null)
1424                {
1425                        return;
1426                }
1427                String[] val = parameters.get(name);
1428                if (val == null)
1429                {
1430                        parameters.put(name, new String[] { value });
1431                }
1432                else
1433                {
1434                        String[] newval = new String[val.length + 1];
1435                        System.arraycopy(val, 0, newval, 0, val.length);
1436                        newval[val.length] = value;
1437                        parameters.put(name, newval);
1438                }
1439        }
1440
1441        /**
1442         * Sets a map of parameters.
1443         * 
1444         * @param parameters
1445         *            the parameters to set
1446         */
1447        public void setParameters(final Map<String, String[]> parameters)
1448        {
1449                this.parameters.putAll(parameters);
1450        }
1451
1452        /**
1453         * Set the path that this request is supposed to be serving. The path is relative to the web
1454         * application root and should start with a / character
1455         * 
1456         * @param path
1457         */
1458        public void setPath(final String path)
1459        {
1460                this.path = UrlDecoder.PATH_INSTANCE.decode(path, getCharset());
1461        }
1462
1463        /**
1464         * Set the complete url for this request. The url will be analyzed.
1465         * 
1466         * @param url
1467         */
1468        public void setURL(String url)
1469        {
1470                setUrl(Url.parse(url));
1471        }
1472
1473        /**
1474         * Helper method to create some default headers for the request
1475         * @param l 
1476         */
1477        private void setDefaultHeaders(Locale l)
1478        {
1479                headers.clear();
1480                addHeader("Accept", "text/xml,application/xml,application/xhtml+xml,"
1481                        + "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5");
1482                addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
1483                addHeader("Accept-Language", l.getLanguage().toLowerCase(Locale.ROOT) + "-"
1484                        + l.getCountry().toLowerCase(Locale.ROOT) + "," + l.getLanguage().toLowerCase(Locale.ROOT) + ";q=0.5");
1485                addHeader("User-Agent",
1486                        "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0");
1487        }
1488
1489        private static final String crlf = "\r\n";
1490        private static final String boundary = "--abcdefgABCDEFG";
1491
1492        private void newAttachment(OutputStream out) throws IOException
1493        {
1494                out.write(boundary.getBytes());
1495                out.write(crlf.getBytes());
1496                out.write("Content-Disposition: form-data".getBytes());
1497        }
1498
1499        /**
1500         * Build the request based on the uploaded files and the parameters.
1501         * 
1502         * @return The request as a string.
1503         */
1504        private byte[] buildRequest()
1505        {
1506                if (uploadedFiles == null && useMultiPartContentType == false)
1507                {
1508                        if (post.getParameterNames().size() == 0)
1509                        {
1510                                return "".getBytes();
1511                        }
1512                        Url url = new Url();
1513                        for (final String parameterName : post.getParameterNames())
1514                        {
1515                                List<StringValue> values = post.getParameterValues(parameterName);
1516                                for (StringValue value : values)
1517                                {
1518                                        url.addQueryParameter(parameterName, value.toString());
1519                                }
1520                        }
1521                        String body = url.toString().substring(1);
1522                        return body.getBytes();
1523                }
1524
1525
1526                try
1527                {
1528                        // Build up the input stream based on the files and parameters
1529                        ByteArrayOutputStream out = new ByteArrayOutputStream();
1530
1531                        // Add parameters
1532                        for (final String parameterName : post.getParameterNames())
1533                        {
1534                                List<StringValue> values = post.getParameterValues(parameterName);
1535                                for (StringValue value : values)
1536                                {
1537                                        newAttachment(out);
1538                                        out.write("; name=\"".getBytes());
1539                                        out.write(parameterName.getBytes());
1540                                        out.write("\"".getBytes());
1541                                        out.write(crlf.getBytes());
1542                                        out.write(crlf.getBytes());
1543                                        out.write(value.toString().getBytes());
1544                                        out.write(crlf.getBytes());
1545                                }
1546                        }
1547
1548                        // Add files
1549                        if (uploadedFiles != null)
1550                        {
1551                                for (Map.Entry<String, List<UploadedFile>> entry : uploadedFiles.entrySet())
1552                                {
1553                                        String fieldName = entry.getKey();
1554                                        List<UploadedFile> files = entry.getValue();
1555
1556                                        for (UploadedFile uf : files)
1557                                        {
1558                                                newAttachment(out);
1559                                                out.write("; name=\"".getBytes());
1560                                                out.write(fieldName.getBytes());
1561                                                out.write("\"; filename=\"".getBytes());
1562                                                if (uf.getFile() != null) {
1563                                                        out.write(uf.getFile().getName().getBytes());
1564                                                }
1565                                                out.write("\"".getBytes());
1566                                                out.write(crlf.getBytes());
1567                                                out.write("Content-Type: ".getBytes());
1568                                                out.write(uf.getContentType().getBytes());
1569                                                out.write(crlf.getBytes());
1570                                                out.write(crlf.getBytes());
1571
1572                                                if (uf.getFile() != null) {
1573                                                        // Load the file and put it into the the inputstream
1574                                                        FileInputStream fis = new FileInputStream(uf.getFile());
1575
1576                                                        try
1577                                                        {
1578                                                                IOUtils.copy(fis, out);
1579                                                        }
1580                                                        finally
1581                                                        {
1582                                                                fis.close();
1583                                                        }
1584                                                }
1585                                                out.write(crlf.getBytes());
1586                                        }
1587                                }
1588                        }
1589
1590                        out.write(boundary.getBytes());
1591                        out.write("--".getBytes());
1592                        out.write(crlf.getBytes());
1593                        return out.toByteArray();
1594                }
1595                catch (IOException e)
1596                {
1597                        // NOTE: IllegalStateException(Throwable) only exists since Java 1.5
1598                        throw new WicketRuntimeException(e);
1599                }
1600        }
1601
1602        /**
1603         * @return local address
1604         */
1605        @Override
1606        public String getLocalAddr()
1607        {
1608                return "127.0.0.1";
1609        }
1610
1611        /**
1612         * @return local host name
1613         */
1614        @Override
1615        public String getLocalName()
1616        {
1617                return "127.0.0.1";
1618        }
1619
1620        /**
1621         * @return local port
1622         */
1623        @Override
1624        public int getLocalPort()
1625        {
1626                return 80;
1627        }
1628
1629        /**
1630         * @return remote port
1631         */
1632        @Override
1633        public int getRemotePort()
1634        {
1635                return 80;
1636        }
1637
1638        /**
1639         * @param url
1640         */
1641        public void setUrl(Url url)
1642        {
1643                if (url.getProtocol() != null)
1644                {
1645                        setScheme(url.getProtocol());
1646                }
1647                if (url.getHost() != null)
1648                {
1649                        serverName = url.getHost();
1650                }
1651                if (url.getPort() != null)
1652                {
1653                        serverPort = url.getPort();
1654                }
1655                String path = url.getPath(getCharset());
1656
1657                if (path.startsWith("/") == false)
1658                {
1659                        path = getContextPath() + getServletPath() + '/' + path;
1660                }
1661                this.url = path;
1662
1663                if (path.startsWith(getContextPath()))
1664                {
1665                        path = path.substring(getContextPath().length());
1666                }
1667                if (path.startsWith(getServletPath()))
1668                {
1669                        path = path.substring(getServletPath().length());
1670                }
1671
1672                setPath(path);
1673
1674                //
1675                // We can't clear the parameters here because users may have set custom
1676                // parameters in request. An better place to clear they is when tester
1677                // setups the next request cycle
1678                //
1679                // parameters.clear();
1680
1681                for (QueryParameter parameter : url.getQueryParameters())
1682                {
1683                        addParameter(parameter.getName(), parameter.getValue());
1684                }
1685        }
1686
1687        /**
1688         * @return request url
1689         */
1690        public Url getUrl()
1691        {
1692                final String urlString;
1693                final String queryString = getQueryString();
1694
1695                if (Strings.isEmpty(queryString))
1696                {
1697                        urlString = getRequestURI();
1698                }
1699                else
1700                {
1701                        urlString = getRequestURI() + '?' + queryString;
1702                }
1703
1704                final Url url = Url.parse(urlString, getCharset());
1705                url.setProtocol(scheme);
1706                url.setHost(serverName);
1707                url.setPort(serverPort);
1708                return url;
1709        }
1710
1711        /**
1712         * @return post parameters
1713         */
1714        public MockRequestParameters getPostParameters()
1715        {
1716                return post;
1717        }
1718
1719        /**
1720         * @return filter prefix
1721         */
1722        public String getFilterPrefix()
1723        {
1724                return getServletPath().substring(1);
1725        }
1726
1727        private final MockRequestParameters post = new MockRequestParameters();
1728
1729        /**
1730         * @return ServletContext
1731         */
1732        @Override
1733        public ServletContext getServletContext()
1734        {
1735                return context;
1736        }
1737
1738        @Override
1739        public AsyncContext startAsync() throws IllegalStateException
1740        {
1741                return null;
1742        }
1743
1744        @Override
1745        public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
1746                throws IllegalStateException
1747        {
1748                return null;
1749        }
1750
1751        @Override
1752        public boolean isAsyncStarted()
1753        {
1754                return false;
1755        }
1756
1757        @Override
1758        public boolean isAsyncSupported()
1759        {
1760                return false;
1761        }
1762
1763        @Override
1764        public AsyncContext getAsyncContext()
1765        {
1766                return null;
1767        }
1768
1769        @Override
1770        public DispatcherType getDispatcherType()
1771        {
1772                return null;
1773        }
1774
1775}