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("myWebXMLParameter"); 111 * URL schedulersConfig = getServletContext().getResource("/WEB-INF/schedulers.xml"); 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 (<context-param>), 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><init-param><param-name>configuration</param-name></code>). If not 721 * found check the servlet context init parameter 722 * <code><context-param><param-name6gt;configuration</param-name></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}