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;
018
019import java.io.UnsupportedEncodingException;
020import java.nio.charset.Charset;
021import java.time.Duration;
022import java.util.Collection;
023import java.util.LinkedList;
024import java.util.Locale;
025import java.util.function.Function;
026
027import javax.servlet.ServletContext;
028import javax.servlet.http.HttpServletRequest;
029import javax.servlet.http.HttpServletResponse;
030import javax.servlet.http.HttpSession;
031
032import org.apache.wicket.Application;
033import org.apache.wicket.Page;
034import org.apache.wicket.RuntimeConfigurationType;
035import org.apache.wicket.Session;
036import org.apache.wicket.WicketRuntimeException;
037import org.apache.wicket.ajax.AjaxRequestHandler;
038import org.apache.wicket.ajax.AjaxRequestTarget;
039import org.apache.wicket.ajax.AjaxRequestTargetListenerCollection;
040import org.apache.wicket.coep.CrossOriginEmbedderPolicyConfiguration;
041import org.apache.wicket.coep.CrossOriginEmbedderPolicyRequestCycleListener;
042import org.apache.wicket.coop.CrossOriginOpenerPolicyConfiguration;
043import org.apache.wicket.coop.CrossOriginOpenerPolicyRequestCycleListener;
044import org.apache.wicket.core.request.mapper.IMapperContext;
045import org.apache.wicket.core.request.mapper.MountedMapper;
046import org.apache.wicket.core.request.mapper.PackageMapper;
047import org.apache.wicket.core.request.mapper.ResourceMapper;
048import org.apache.wicket.core.util.file.WebApplicationPath;
049import org.apache.wicket.core.util.resource.ClassPathResourceFinder;
050import org.apache.wicket.csp.CSPHeaderConfiguration;
051import org.apache.wicket.csp.ContentSecurityPolicySettings;
052import org.apache.wicket.markup.MarkupType;
053import org.apache.wicket.markup.head.CssHeaderItem;
054import org.apache.wicket.markup.head.JavaScriptHeaderItem;
055import org.apache.wicket.markup.html.WebPage;
056import org.apache.wicket.markup.html.form.AutoLabelResolver;
057import org.apache.wicket.markup.html.form.AutoLabelTextResolver;
058import org.apache.wicket.markup.html.pages.AccessDeniedPage;
059import org.apache.wicket.markup.html.pages.InternalErrorPage;
060import org.apache.wicket.markup.html.pages.PageExpiredErrorPage;
061import org.apache.wicket.markup.resolver.AutoLinkResolver;
062import org.apache.wicket.protocol.http.servlet.AbstractRequestWrapperFactory;
063import org.apache.wicket.protocol.http.servlet.FilterFactoryManager;
064import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
065import org.apache.wicket.protocol.http.servlet.ServletWebResponse;
066import org.apache.wicket.request.IRequestHandler;
067import org.apache.wicket.request.IRequestMapper;
068import org.apache.wicket.request.Request;
069import org.apache.wicket.request.Response;
070import org.apache.wicket.request.Url;
071import org.apache.wicket.request.cycle.RequestCycle;
072import org.apache.wicket.request.handler.render.WebPageRenderer;
073import org.apache.wicket.request.http.WebRequest;
074import org.apache.wicket.request.http.WebResponse;
075import org.apache.wicket.request.mapper.ICompoundRequestMapper;
076import org.apache.wicket.request.mapper.IRequestMapperDelegate;
077import org.apache.wicket.request.resource.CssResourceReference;
078import org.apache.wicket.request.resource.JavaScriptResourceReference;
079import org.apache.wicket.request.resource.ResourceReference;
080import org.apache.wicket.resource.bundles.ReplacementResourceBundleReference;
081import org.apache.wicket.session.HttpSessionStore;
082import org.apache.wicket.util.crypt.CharEncoding;
083import org.apache.wicket.util.file.FileCleaner;
084import org.apache.wicket.util.file.IFileCleaner;
085import org.apache.wicket.util.file.Path;
086import org.apache.wicket.util.lang.Args;
087import org.apache.wicket.util.lang.PackageName;
088import org.apache.wicket.util.string.Strings;
089import org.apache.wicket.util.watch.IModificationWatcher;
090import org.slf4j.Logger;
091import org.slf4j.LoggerFactory;
092
093
094/**
095 * A web application is a subclass of Application which associates with an instance of WicketServlet
096 * to serve pages over the HTTP protocol. This class is intended to be subclassed by framework
097 * clients to define a web application.
098 * <p>
099 * Application settings are given defaults by the WebApplication() constructor and internalInit
100 * method, such as error page classes appropriate for HTML. WebApplication subclasses can override
101 * these values and/or modify other application settings by overriding the init() method and then by
102 * calling getXXXSettings() to retrieve an interface to a mutable Settings object. Do not do this in
103 * the constructor itself because the defaults will then override your settings.
104 * <p>
105 * If you want to use a filter specific configuration, e.g. using init parameters from the
106 * {@link javax.servlet.FilterConfig} object, you should override the init() method. For example:
107 * 
108 * <pre>
109 *  public void init() {
110 *  String webXMLParameter = getInitParameter(&quot;myWebXMLParameter&quot;);
111 *  URL schedulersConfig = getServletContext().getResource(&quot;/WEB-INF/schedulers.xml&quot;);
112 *  ...
113 * </pre>
114 * 
115 * @see WicketFilter
116 * @see org.apache.wicket.settings.ApplicationSettings
117 * @see org.apache.wicket.settings.DebugSettings
118 * @see org.apache.wicket.settings.ExceptionSettings
119 * @see org.apache.wicket.settings.MarkupSettings
120 * @see org.apache.wicket.settings.PageSettings
121 * @see org.apache.wicket.settings.RequestCycleSettings
122 * @see org.apache.wicket.settings.ResourceSettings
123 * @see org.apache.wicket.settings.SecuritySettings
124 * @see javax.servlet.Filter
125 * @see javax.servlet.FilterConfig
126 * @see javax.servlet.ServletContext
127 * 
128 * @author Jonathan Locke
129 * @author Chris Turner
130 * @author Johan Compagner
131 * @author Eelco Hillenius
132 * @author Juergen Donnerstag
133 */
134public abstract class WebApplication extends Application
135{
136        /** Log. */
137        private static final Logger log = LoggerFactory.getLogger(WebApplication.class);
138
139        public static final String META_INF_RESOURCES = "META-INF/resources";
140
141        private ServletContext servletContext;
142
143        private final AjaxRequestTargetListenerCollection ajaxRequestTargetListeners;
144
145        private Function<Page, AjaxRequestTarget> ajaxRequestTargetProvider;
146
147        private FilterFactoryManager filterFactoryManager;
148
149        /**
150         * Cached value of the parsed (from system properties or Servlet init/context parameter)
151         * <code>wicket.configuration</code> setting. No need to re-read it because it wont change at
152         * runtime.
153         */
154        private RuntimeConfigurationType configurationType;
155        
156        private ContentSecurityPolicySettings cspSettings;
157
158        /**
159         * Covariant override for easy getting the current {@link WebApplication} without having to cast
160         * it.
161         */
162        public static WebApplication get()
163        {
164                Application application = Application.get();
165
166                if (application instanceof WebApplication == false)
167                {
168                        throw new WicketRuntimeException(
169                                "The application attached to the current thread is not a " +
170                                        WebApplication.class.getSimpleName());
171                }
172
173                return (WebApplication)application;
174        }
175
176        /**
177         * the prefix for storing variables in the actual session (typically {@link HttpSession} for
178         * this application instance.
179         */
180        private String sessionAttributePrefix;
181
182        /** The WicketFilter that this application is attached to */
183        private WicketFilter wicketFilter;
184
185        /**
186         * Constructor. <strong>Use {@link #init()} for any configuration of your application instead of
187         * overriding the constructor.</strong>
188         */
189        public WebApplication()
190        {
191                ajaxRequestTargetListeners = new AjaxRequestTargetListenerCollection();
192        }
193
194        /**
195         * @see org.apache.wicket.Application#getApplicationKey()
196         */
197        @Override
198        public final String getApplicationKey()
199        {
200                return getName();
201        }
202
203        /**
204         * Gets an init parameter of the filter, or null if the parameter does not exist.
205         * 
206         * @param key
207         *            the key to search for
208         * @return the value of the filter init parameter
209         */
210        public String getInitParameter(String key)
211        {
212                if (wicketFilter != null)
213                {
214                        return wicketFilter.getFilterConfig().getInitParameter(key);
215                }
216                throw new IllegalStateException("init parameter '" + key +
217                        "' is not set yet. Any code in your" +
218                        " Application object that uses the wicketServlet/Filter instance should be put" +
219                        " in the init() method instead of your constructor");
220        }
221
222
223        /**
224         * Sets servlet context this application runs after. This is uaully done from a filter or a
225         * servlet responsible for managing this application object, such as {@link WicketFilter}
226         * 
227         * @param servletContext
228         */
229        public void setServletContext(ServletContext servletContext)
230        {
231                this.servletContext = servletContext;
232        }
233
234        /**
235         * Gets the servlet context for this application. Use this to get references to absolute paths,
236         * global web.xml parameters (&lt;context-param&gt;), etc.
237         * 
238         * @return The servlet context for this application
239         */
240        public ServletContext getServletContext()
241        {
242                if (servletContext == null)
243                {
244                        throw new IllegalStateException("servletContext is not set yet. Any code in your"
245                                + " Application object that uses the wicket filter instance should be put"
246                                + " in the init() method instead of your constructor");
247                }
248                return servletContext;
249        }
250
251        /**
252         * Gets the prefix for storing variables in the actual session (typically {@link HttpSession}
253         * for this application instance.
254         * 
255         * @param request
256         *            the request
257         * @param filterName
258         *            If null, than it defaults to the WicketFilter filter name. However according to
259         *            the ServletSpec the Filter is not guaranteed to be initialized e.g. when
260         *            WicketSessionFilter gets initialized. Thus, though you (and WicketSessionFilter)
261         *            can provide a filter name, you must make sure it is the same as WicketFilter will
262         *            provide once initialized.
263         * 
264         * @return the prefix for storing variables in the actual session
265         */
266        public String getSessionAttributePrefix(final WebRequest request, String filterName)
267        {
268                if (sessionAttributePrefix == null)
269                {
270                        if (filterName == null)
271                        {
272                                // According to the ServletSpec, the filter might not yet been initialized
273                                filterName = getWicketFilter().getFilterConfig().getFilterName();
274                        }
275                        String namespace = getMapperContext().getNamespace();
276                        sessionAttributePrefix = namespace + ':' + filterName + ':';
277                }
278
279                // Namespacing for session attributes is provided by
280                // adding the servlet path
281                return sessionAttributePrefix;
282        }
283
284        /**
285         * @return The Wicket filter for this application
286         */
287        public final WicketFilter getWicketFilter()
288        {
289                return wicketFilter;
290        }
291
292        /**
293         * @see org.apache.wicket.Application#logEventTarget(org.apache.wicket.request.IRequestHandler)
294         */
295        @Override
296        public void logEventTarget(IRequestHandler target)
297        {
298                super.logEventTarget(target);
299                IRequestLogger rl = getRequestLogger();
300                if (rl != null)
301                {
302                        rl.logEventTarget(target);
303                }
304        }
305
306        /**
307         * @see org.apache.wicket.Application#logResponseTarget(org.apache.wicket.request.IRequestHandler)
308         */
309        @Override
310        public void logResponseTarget(IRequestHandler target)
311        {
312                super.logResponseTarget(target);
313                IRequestLogger rl = getRequestLogger();
314                if (rl != null)
315                {
316                        rl.logResponseTarget(target);
317                }
318        }
319
320        /**
321         * Mounts an encoder at the given path.
322         * 
323         * @param mapper
324         *            the encoder that will be used for this mount
325         */
326        public void mount(final IRequestMapper mapper)
327        {
328                Args.notNull(mapper, "mapper");
329                getRootRequestMapperAsCompound().add(mapper);
330        }
331
332        /**
333         * Mounts a page class to the given path.
334         * 
335         * <p>
336         * NOTE: mount path must not start with reserved URL segments! See {@link IMapperContext} to know
337         * which segments are reserved for internal use.
338         * </p>
339         * @param <T>
340         *            type of page
341         * 
342         * @param path
343         *            the path to mount the page class on
344         * @param pageClass
345         *            the page class to be mounted
346         */
347        public <T extends Page> MountedMapper mountPage(final String path, final Class<T> pageClass)
348        {
349                MountedMapper mapper = new MountedMapper(path, pageClass);
350                mount(mapper);
351                return mapper;
352        }
353
354        /**
355         * Mounts a shared resource to the given path.
356         * 
357         * <p>
358         * NOTE: mount path must not start with reserved URL segments! See {@link IMapperContext} to know
359         * which segments are reserved for internal use.
360         * </p>
361         * @param path
362         *            the path to mount the resource reference on
363         * @param reference
364         *            resource reference to be mounted
365         */
366        public ResourceMapper mountResource(final String path, final ResourceReference reference)
367        {
368                if (reference.canBeRegistered())
369                {
370                        getResourceReferenceRegistry().registerResourceReference(reference);
371                }
372                ResourceMapper mapper = new ResourceMapper(path, reference);
373                mount(mapper);
374                return mapper;
375        }
376
377        /**
378         * Mounts mounts all bookmarkable pages in a the pageClass's package to the given path.
379         *
380         * <p>
381         * NOTE: mount path must not start with reserved URL segments! See {@link IMapperContext} to know
382         * which segments are reserved for internal use.
383         * </p>
384         * @param <P>
385         *            type of page
386         * 
387         * @param path
388         *            the path to mount the page class on
389         * @param pageClass
390         *            the page class to be mounted
391         */
392        public <P extends Page> PackageMapper mountPackage(final String path, final Class<P> pageClass)
393        {
394                PackageMapper packageMapper = new PackageMapper(path, PackageName.forClass(pageClass));
395                mount(packageMapper);
396                return packageMapper;
397        }
398
399        /**
400         * Unregisters all {@link IRequestMapper}s which would match on a this path.
401         * <p>
402         * Useful in OSGi environments where a bundle may want to update the mount point.
403         * </p>
404         * 
405         * @param path
406         *            the path to unmount
407         */
408        public void unmount(String path)
409        {
410                Args.notNull(path, "path");
411
412                if (path.charAt(0) == '/')
413                {
414                        path = path.substring(1);
415                }
416
417                IRequestMapper mapper = getRootRequestMapper();
418
419                while (mapper instanceof IRequestMapperDelegate)
420                {
421                        mapper = ((IRequestMapperDelegate) mapper).getDelegateMapper();
422                }
423
424                /*
425                 * Only attempt to unmount if root request mapper is either a compound, or wraps a compound to avoid leaving the
426                 * application with no mappers installed.
427                 */
428                if (mapper instanceof ICompoundRequestMapper)
429                {
430                        final Url url = Url.parse(path);
431
432                        Request request = new Request()
433                        {
434                                @Override
435                                public Url getUrl()
436                                {
437                                        return url;
438                                }
439
440                                @Override
441                                public Url getClientUrl()
442                                {
443                                        return url;
444                                }
445
446                                @Override
447                                public Locale getLocale()
448                                {
449                                        return null;
450                                }
451
452                                @Override
453                                public Charset getCharset()
454                                {
455                                        return null;
456                                }
457
458                                @Override
459                                public Object getContainerRequest()
460                                {
461                                        return null;
462                                }
463                        };
464
465                        unmountFromCompound((ICompoundRequestMapper) mapper, request);
466                }
467        }
468
469        /**
470         * Descends the tree of {@link ICompoundRequestMapper}s and {@link IRequestMapperDelegate}s to find the correct descendant
471         * to remove.
472         *
473         * @param parent
474         *              The {@link ICompoundRequestMapper} from which to unmount the matching mapper.
475         * @param request
476         *              The request used to find the mapper to remove.
477         */
478        private void unmountFromCompound(ICompoundRequestMapper parent, Request request)
479        {
480                Collection<IRequestMapper> toRemove = new LinkedList<>();
481
482                for (IRequestMapper mapper : parent)
483                {
484                        if (mapper.mapRequest(request) != null)
485                        {
486                                IRequestMapper actualMapper = mapper;
487
488                                while (actualMapper instanceof IRequestMapperDelegate)
489                                {
490                                        actualMapper = ((IRequestMapperDelegate) actualMapper).getDelegateMapper();
491                                }
492
493                                if (actualMapper instanceof ICompoundRequestMapper)
494                                {
495                                        unmountFromCompound((ICompoundRequestMapper) actualMapper, request);
496                                }
497                                else
498                                {
499                                        toRemove.add(mapper);
500                                }
501                        }
502                }
503
504                for (IRequestMapper mapper : toRemove)
505                {
506                        parent.remove(mapper);
507                }
508        }
509
510        /**
511         * Registers a replacement resource for the given javascript resource. This replacement can be
512         * another {@link JavaScriptResourceReference} for a packaged resource, but it can also be an
513         * {@link org.apache.wicket.request.resource.UrlResourceReference} to replace the resource by a
514         * resource hosted on a CDN. Registering a replacement will cause the resource to replaced by
515         * the given resource throughout the application: if {@code base} is added, {@code replacement}
516         * will be added instead.
517         * 
518         * @param base
519         *            The resource to replace
520         * @param replacement
521         *            The replacement
522         */
523        public final void addResourceReplacement(JavaScriptResourceReference base,
524                ResourceReference replacement)
525        {
526                ReplacementResourceBundleReference bundle = new ReplacementResourceBundleReference(replacement);
527                bundle.addProvidedResources(JavaScriptHeaderItem.forReference(base));
528                getResourceBundles().addBundle(JavaScriptHeaderItem.forReference(bundle));
529        }
530
531        /**
532         * Registers a replacement resource for the given CSS resource. This replacement can be another
533         * {@link CssResourceReference} for a packaged resource, but it can also be an
534         * {@link org.apache.wicket.request.resource.UrlResourceReference} to replace the resource by a
535         * resource hosted on a CDN. Registering a replacement will cause the resource to replaced by
536         * the given resource throughout the application: if {@code base} is added, {@code replacement}
537         * will be added instead.
538         * 
539         * @param base
540         *            The resource to replace
541         * @param replacement
542         *            The replacement
543         */
544        public final void addResourceReplacement(CssResourceReference base,
545                ResourceReference replacement)
546        {
547                ReplacementResourceBundleReference bundle = new ReplacementResourceBundleReference(replacement);
548                bundle.addProvidedResources(CssHeaderItem.forReference(base));
549                getResourceBundles().addBundle(CssHeaderItem.forReference(bundle));
550        }
551
552        /**
553         * Create a new WebRequest. Subclasses of WebRequest could e.g. decode and obfuscate URL which
554         * has been encoded by an appropriate WebResponse.
555         * 
556         * @param servletRequest
557         *            the current HTTP Servlet request
558         * @param filterPath
559         *            the filter mapping read from web.xml
560         * @return a WebRequest object
561         */
562        public WebRequest newWebRequest(HttpServletRequest servletRequest, final String filterPath)
563        {
564                return new ServletWebRequest(servletRequest, filterPath);
565        }
566
567        /**
568         * Pre- and post- configures the {@link WebRequest} created by user override-able
569         * {@link #newWebRequest(HttpServletRequest, String)}
570         * 
571         * @param servletRequest
572         *            the current HTTP Servlet request
573         * @param filterPath
574         *            the filter mapping read from web.xml
575         * @return a WebRequest object
576         */
577        WebRequest createWebRequest(HttpServletRequest servletRequest, final String filterPath)
578        {
579                if (hasFilterFactoryManager())
580                {
581                        for (AbstractRequestWrapperFactory factory : getFilterFactoryManager())
582                        {
583                                servletRequest = factory.getWrapper(servletRequest);
584                        }
585                }
586
587                WebRequest webRequest = newWebRequest(servletRequest, filterPath);
588
589                if (servletRequest.getCharacterEncoding() == null)
590                {
591                        try
592                        {
593                                if (webRequest.isAjax())
594                                {
595                                        // WICKET-3908, WICKET-1816: Forms submitted with Ajax are always UTF-8 encoded
596                                        servletRequest.setCharacterEncoding(CharEncoding.UTF_8);
597                                }
598                                else
599                                {
600                                        String requestEncoding = getRequestCycleSettings().getResponseRequestEncoding();
601                                        servletRequest.setCharacterEncoding(requestEncoding);
602                                }
603                        }
604                        catch (UnsupportedEncodingException e)
605                        {
606                                throw new WicketRuntimeException(e);
607                        }
608                }
609
610                return webRequest;
611        }
612
613        /**
614         * Creates a WebResponse. Subclasses of WebRequest could e.g. encode wicket's default URL and
615         * hide the details from the user. A appropriate WebRequest must be implemented and configured
616         * to decode the encoded URL.
617         * 
618         * @param webRequest
619         *            the {@link WebRequest} that will handle the current HTTP Servlet request
620         * @param httpServletResponse
621         *            the current HTTP Servlet response
622         * @return a WebResponse object
623         */
624        protected WebResponse newWebResponse(final WebRequest webRequest,
625                final HttpServletResponse httpServletResponse)
626        {
627                return new ServletWebResponse((ServletWebRequest)webRequest, httpServletResponse);
628        }
629
630        /**
631         * Pre- and post- configures the {@link WebResponse} returned from
632         * {@link #newWebResponse(WebRequest, HttpServletResponse)}
633         * 
634         * @param webRequest
635         *            the {@link WebRequest} that will handle the current HTTP Servlet request
636         * @param httpServletResponse
637         *            the current HTTP Servlet response
638         * @return the configured WebResponse object
639         */
640        WebResponse createWebResponse(final WebRequest webRequest,
641                final HttpServletResponse httpServletResponse)
642        {
643                WebResponse webResponse = newWebResponse(webRequest, httpServletResponse);
644
645                boolean shouldBufferResponse = getRequestCycleSettings().getBufferResponse();
646                return shouldBufferResponse ? new HeaderBufferingWebResponse(webResponse) : webResponse;
647        }
648
649        @Override
650        public Session newSession(Request request, Response response)
651        {
652                return new WebSession(request);
653        }
654
655        @Override
656        public void sessionUnbound(final String sessionId)
657        {
658                super.sessionUnbound(sessionId);
659
660                IRequestLogger logger = getRequestLogger();
661                if (logger != null)
662                {
663                        logger.sessionDestroyed(sessionId);
664                }
665        }
666
667        /**
668         * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
669         * 
670         * @param wicketFilter
671         *            The wicket filter instance for this application
672         */
673        public final void setWicketFilter(final WicketFilter wicketFilter)
674        {
675                Args.notNull(wicketFilter, "wicketFilter");
676                this.wicketFilter = wicketFilter;
677                servletContext = wicketFilter.getFilterConfig().getServletContext();
678        }
679
680        /**
681         * Initialize; if you need the wicket servlet/filter for initialization, e.g. because you want
682         * to read an initParameter from web.xml or you want to read a resource from the servlet's
683         * context path, you can override this method and provide custom initialization. This method is
684         * called right after this application class is constructed, and the wicket servlet/filter is
685         * set. <strong>Use this method for any application setup instead of the constructor.</strong>
686         */
687        @Override
688        protected void init()
689        {
690                super.init();
691        }
692
693        /**
694         * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
695         */
696        @Override
697        public void internalDestroy()
698        {
699                // destroy the resource watcher
700                IModificationWatcher resourceWatcher = getResourceSettings().getResourceWatcher(false);
701                if (resourceWatcher != null)
702                {
703                        resourceWatcher.destroy();
704                }
705
706                IFileCleaner fileCleaner = getResourceSettings().getFileCleaner();
707                if (fileCleaner != null)
708                {
709                        fileCleaner.destroy();
710                }
711
712                super.internalDestroy();
713        }
714
715        /**
716         * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
717         * 
718         * Internal initialization. First determine the deployment mode. First check the system property
719         * -Dwicket.configuration. If it does not exist check the servlet init parameter (
720         * <code>&lt;init-param&gt;&lt;param-name&gt;configuration&lt;/param-name&gt;</code>). If not
721         * found check the servlet context init parameter
722         * <code>&lt;context-param&gt;&lt;param-name6gt;configuration&lt;/param-name&gt;</code>). If the
723         * parameter is "development" (which is default), settings appropriate for development are set.
724         * If it's "deployment" , deployment settings are used. If development is specified and a
725         * "sourceFolder" init parameter is also set, then resources in that folder will be polled for
726         * changes.
727         */
728        @Override
729        protected void internalInit()
730        {
731                super.internalInit();
732
733                getResourceSettings().getResourceFinders().add(
734                        new WebApplicationPath(getServletContext(), ""));
735                getResourceSettings().getResourceFinders().add(
736                        new ClassPathResourceFinder(META_INF_RESOURCES));
737
738                // Set default error pages for HTML markup
739                getApplicationSettings().setPageExpiredErrorPage(PageExpiredErrorPage.class);
740                getApplicationSettings().setInternalErrorPage(InternalErrorPage.class);
741                getApplicationSettings().setAccessDeniedPage(AccessDeniedPage.class);
742
743                // Add resolver for automatically resolving HTML links
744                getPageSettings().addComponentResolver(new AutoLinkResolver());
745                getPageSettings().addComponentResolver(new AutoLabelResolver());
746                getPageSettings().addComponentResolver(new AutoLabelTextResolver());
747
748                getResourceSettings().setFileCleaner(new FileCleaner());
749
750                setPageRendererProvider(WebPageRenderer::new);
751                setSessionStoreProvider(HttpSessionStore::new);
752                setAjaxRequestTargetProvider(AjaxRequestHandler::new);
753
754                AjaxRequestTargetListenerCollection ajaxRequestTargetListeners = getAjaxRequestTargetListeners();
755                ajaxRequestTargetListeners.add(new AjaxEnclosureListener());
756                ajaxRequestTargetListeners.add(new MultipartFormComponentListener());
757
758                // Configure the app.
759                configure();
760                if (getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT)
761                {
762                        // Add optional sourceFolder for resources.
763                        String resourceFolder = getInitParameter("sourceFolder");
764                        if (resourceFolder != null)
765                        {
766                                getResourceSettings().getResourceFinders().add(new Path(resourceFolder));
767                        }
768                        getCspSettings().blocking().reportBack();
769                }
770                getCspSettings().blocking().strict();
771        }
772
773        @Override
774        protected void validateInit()
775        {
776                super.validateInit();
777
778                if (getCspSettings().isEnabled()) {
779                        getCspSettings().enforce(this);
780                }
781
782                // enable coop and coep listeners if specified in security settings
783                CrossOriginOpenerPolicyConfiguration coopConfig = getSecuritySettings()
784                        .getCrossOriginOpenerPolicyConfiguration();
785                if (coopConfig.isEnabled())
786                {
787                        getRequestCycleListeners()
788                                .add(new CrossOriginOpenerPolicyRequestCycleListener(coopConfig));
789                }
790
791                CrossOriginEmbedderPolicyConfiguration coepConfig = getSecuritySettings()
792                        .getCrossOriginEmbedderPolicyConfiguration();
793                if (coepConfig.isEnabled())
794                {
795                        getRequestCycleListeners()
796                                .add(new CrossOriginEmbedderPolicyRequestCycleListener(coepConfig));
797                }
798        }
799
800        /**
801         * set runtime configuration type
802         * <p/>
803         * this is a write-once property: once configured it can not be changed later on.
804         * 
805         * @param configurationType
806         */
807        public Application setConfigurationType(RuntimeConfigurationType configurationType)
808        {
809                if (this.configurationType != null)
810                {
811                        throw new IllegalStateException(
812                                "Configuration type is write-once. You can not change it. " + "" +
813                                        "Current value='" + configurationType + "'");
814                }
815                this.configurationType = Args.notNull(configurationType, "configurationType");
816                return this;
817        }
818
819        /**
820         * {@inheritDoc}
821         */
822        @Override
823        public RuntimeConfigurationType getConfigurationType()
824        {
825                if (configurationType == null)
826                {
827                        String result = null;
828                        try
829                        {
830                                result = System.getProperty("wicket." + Application.CONFIGURATION);
831                        }
832                        catch (SecurityException e)
833                        {
834                                log.warn("SecurityManager doesn't allow to read the configuration type from " +
835                                                "the system properties. The configuration type will be read from the web.xml.");
836                        }
837
838                        // If no system parameter check filter/servlet <init-param> and <context-param>
839                        if (result == null)
840                        {
841                                result = getInitParameter("wicket." + Application.CONFIGURATION);
842                        }
843                        if (result == null)
844                        {
845                                result = getServletContext().getInitParameter("wicket." + Application.CONFIGURATION);
846                        }
847
848                        // If no system parameter check filter/servlet specific <init-param>
849                        if (result == null)
850                        {
851                                result = getInitParameter(Application.CONFIGURATION);
852                        }
853
854                        // If no system parameter and no <init-param>, then check
855                        // <context-param>
856                        if (result == null)
857                        {
858                                result = getServletContext().getInitParameter(Application.CONFIGURATION);
859                        }
860
861                        // Return result if we have found it, else fall back to DEVELOPMENT mode
862                        // as the default.
863                        if (result != null)
864                        {
865                                try
866                                {
867                                        configurationType = RuntimeConfigurationType.valueOf(result.toUpperCase(Locale.ROOT));
868                                }
869                                catch (IllegalArgumentException e)
870                                {
871                                        // Ignore : fall back to DEVELOPMENT mode
872                                        // log.warn("Unknown runtime configuration type '" + result +
873                                        // "', falling back to DEVELOPMENT mode.");
874                                        throw new IllegalArgumentException("Invalid configuration type: '" + result +
875                                                "'.  Must be \"development\" or \"deployment\".");
876                                }
877                        }
878                }
879
880                if (configurationType == null)
881                {
882                        configurationType = RuntimeConfigurationType.DEVELOPMENT;
883                }
884
885                return configurationType;
886        }
887
888        /**
889         * The rules if and when to insert an xml decl in the response are a bit tricky. Hence, we allow
890         * the user to replace the default implementation per page and per application.
891         * <p>
892         * Default implementation: the page mime type must be "application/xhtml+xml" and request
893         * HTTP_ACCEPT header must include "application/xhtml+xml" to automatically include the xml
894         * decl. Please see <a href=
895         * "https://developer.mozilla.org/en/Writing_JavaScript_for_XHTML#Finally:_Content_Negotiation"
896         * >Writing JavaScript for XHTML</a> for details.
897         * <p>
898         * Please note that xml decls in Wicket's markup are only used for reading the markup. The
899         * markup's xml decl will always be removed and never be used to configure the response.
900         * 
901         * @param page
902         *            The page currently being rendered
903         * @param insert
904         *            If false, than the rules are applied. If true, it'll always be written. In order
905         *            to never insert it, than subclass renderXmlDecl() with an empty implementation.
906         */
907        public void renderXmlDecl(final WebPage page, boolean insert)
908        {
909                if (insert || MarkupType.XML_MIME.equalsIgnoreCase(page.getMarkupType().getMimeType()))
910                {
911                        final RequestCycle cycle = RequestCycle.get();
912
913                        if (insert == false)
914                        {
915                                WebRequest request = (WebRequest)cycle.getRequest();
916
917                                String accept = request.getHeader("Accept");
918                                insert = ((accept == null) || (accept.indexOf(MarkupType.XML_MIME) != -1));
919                        }
920
921                        if (insert)
922                        {
923                                WebResponse response = (WebResponse)cycle.getResponse();
924                                response.write("<?xml version='1.0'");
925                                String encoding = getRequestCycleSettings().getResponseRequestEncoding();
926                                if (Strings.isEmpty(encoding) == false)
927                                {
928                                        response.write(" encoding='");
929                                        response.write(encoding);
930                                        response.write("'");
931                                }
932                                response.write(" ?>");
933                        }
934                }
935        }
936
937        /**
938         * Creates a new ajax request target used to control ajax responses
939         * 
940         * @param page
941         *            page on which ajax response is made
942         * @return non-null ajax request target instance
943         */
944        public final AjaxRequestTarget newAjaxRequestTarget(final Page page)
945        {
946                AjaxRequestTarget target = getAjaxRequestTargetProvider().apply(page);
947                for (AjaxRequestTarget.IListener listener : ajaxRequestTargetListeners)
948                {
949                        target.addListener(listener);
950                }
951                return target;
952        }
953
954        /**
955         * Log that this application is started.
956         */
957        final void logStarted()
958        {
959                if (log.isInfoEnabled())
960                {
961                        String version = getFrameworkSettings().getVersion();
962                        StringBuilder b = new StringBuilder();
963                        b.append("[").append(getName()).append("] Started Wicket ");
964                        if (!"n/a".equals(version))
965                        {
966                                b.append("version ").append(version).append(" ");
967                        }
968                        b.append("in ").append(getConfigurationType()).append(" mode");
969                        log.info(b.toString());
970                }
971
972                if (usesDevelopmentConfig())
973                {
974                        outputDevelopmentModeWarning();
975                }
976        }
977
978        /**
979         * This method prints a warning to stderr that we are starting in development mode.
980         * <p>
981         * If you really need to test Wicket in development mode on a staging server somewhere and are
982         * annoying the sysadmin for it with stderr messages, you can override this to make it do
983         * something else.
984         */
985        protected void outputDevelopmentModeWarning()
986        {
987                System.err.print("********************************************************************\n"
988                        + "*** WARNING: Wicket is running in DEVELOPMENT mode.              ***\n"
989                        + "***                               ^^^^^^^^^^^                    ***\n"
990                        + "*** Do NOT deploy to your live server(s) without changing this.  ***\n"
991                        + "*** See Application#getConfigurationType() for more information. ***\n"
992                        + "********************************************************************\n");
993        }
994
995        /*
996         * Can contain at most 1000 responses and each entry can live at most one minute. For now there
997         * is no need to configure these parameters externally.
998         */
999        private final StoredResponsesMap storedResponses = new StoredResponsesMap(1000,
1000                Duration.ofSeconds(60));
1001
1002        /**
1003         * 
1004         * @param sessionId
1005         * @param url
1006         * @return true if has buffered response
1007         */
1008        public boolean hasBufferedResponse(String sessionId, Url url)
1009        {
1010                String key = sessionId + url.toString();
1011                return storedResponses.containsKey(key);
1012        }
1013
1014        /**
1015         * Retrieves a stored buffered response for a given sessionId and url.
1016         *
1017         * @param url
1018         *          The url used as a key
1019         * @return the stored buffered response. {@code null} if there is no stored response for the given url
1020         * @see org.apache.wicket.settings.RequestCycleSettings.RenderStrategy#REDIRECT_TO_BUFFER
1021         */
1022        public BufferedWebResponse getAndRemoveBufferedResponse(String sessionId, Url url)
1023        {
1024                String key = sessionId + url.toString();
1025                return storedResponses.remove(key);
1026        }
1027
1028        /**
1029         * Store the buffered response at application level to use it at a later time.
1030         *
1031         * @param sessionId
1032         * @param url
1033         * @param response
1034         */
1035        public void storeBufferedResponse(String sessionId, Url url, BufferedWebResponse response)
1036        {
1037                if (Strings.isEmpty(sessionId))
1038                {
1039                        log.error("storeBufferedResponse needs a valid session id to store the response, but a null one was found. "
1040                                        + "Please report the problem to dev team and try to reproduce it in a quickstart project.");
1041                        return;
1042                }
1043
1044                String key = sessionId + url.toString();
1045                storedResponses.put(key, response);
1046        }
1047
1048        @Override
1049        public String getMimeType(String fileName)
1050        {
1051                String mimeType = getServletContext().getMimeType(fileName);
1052                return mimeType != null ? mimeType : super.getMimeType(fileName);
1053        }
1054
1055        /**
1056         * Returns the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects.
1057         * 
1058         * @return the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects.
1059         */
1060        public Function<Page, AjaxRequestTarget> getAjaxRequestTargetProvider()
1061        {
1062                return ajaxRequestTargetProvider;
1063        }
1064
1065        /**
1066         * Sets the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects.
1067         * 
1068         * @param ajaxRequestTargetProvider
1069         *            the new provider
1070         */
1071        public Application setAjaxRequestTargetProvider(
1072                Function<Page, AjaxRequestTarget> ajaxRequestTargetProvider)
1073        {
1074                this.ajaxRequestTargetProvider = ajaxRequestTargetProvider;
1075                return this;
1076        }
1077
1078        /**
1079         * Returns the registered {@link org.apache.wicket.ajax.AjaxRequestTarget.IListener} objects.
1080         * 
1081         * @return the registered {@link org.apache.wicket.ajax.AjaxRequestTarget.IListener} objects.
1082         */
1083        public AjaxRequestTargetListenerCollection getAjaxRequestTargetListeners()
1084        {
1085                return ajaxRequestTargetListeners;
1086        }
1087
1088        /**
1089         * @return True if at least one filter factory has been added.
1090         */
1091        public final boolean hasFilterFactoryManager()
1092        {
1093                return filterFactoryManager != null;
1094        }
1095
1096        /**
1097         * @return The filter factory manager
1098         */
1099        public final FilterFactoryManager getFilterFactoryManager()
1100        {
1101                if (filterFactoryManager == null)
1102                {
1103                        filterFactoryManager = new FilterFactoryManager();
1104                }
1105                return filterFactoryManager;
1106        }
1107        
1108        /**
1109         * TODO remove in Wicket 10
1110         * 
1111         * @deprecated use {@link #setCspSettings(ContentSecurityPolicySettings)} instead
1112         */
1113        protected ContentSecurityPolicySettings newCspSettings()
1114        {
1115                return new ContentSecurityPolicySettings(this);
1116        }
1117
1118        /**
1119         * Returns the {@link ContentSecurityPolicySettings} for this application. See
1120         * {@link ContentSecurityPolicySettings} and {@link CSPHeaderConfiguration} for instructions on
1121         * configuring the CSP for your specific needs.
1122         * 
1123         * @return The {@link ContentSecurityPolicySettings} for this application.
1124         * @see ContentSecurityPolicySettings
1125         * @see CSPHeaderConfiguration
1126         * 
1127         * TODO make final in Wicket 10
1128         */
1129        public ContentSecurityPolicySettings getCspSettings()
1130        {
1131                checkSettingsAvailable();
1132
1133                if (cspSettings == null)
1134                {
1135                        cspSettings = newCspSettings();
1136                }
1137                return cspSettings;
1138        }
1139
1140        /**
1141         * Set CSP settings.
1142         * 
1143         */
1144        public void setCspSettings(ContentSecurityPolicySettings cspSettings)
1145        {
1146                this.cspSettings = cspSettings;
1147        }
1148}