View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.lifecycle;
20  
21  import java.io.IOException;
22  import java.net.URI;
23  import java.net.URISyntaxException;
24  import java.util.logging.Level;
25  import java.util.logging.Logger;
26  
27  import javax.el.MethodExpression;
28  import javax.faces.FacesException;
29  import javax.faces.FactoryFinder;
30  import javax.faces.application.Application;
31  import javax.faces.application.ProjectStage;
32  import javax.faces.application.ProtectedViewException;
33  import javax.faces.application.ViewExpiredException;
34  import javax.faces.application.ViewHandler;
35  import javax.faces.component.UIViewRoot;
36  import javax.faces.context.ExternalContext;
37  import javax.faces.context.FacesContext;
38  import javax.faces.event.PhaseEvent;
39  import javax.faces.event.PhaseId;
40  import javax.faces.event.PostAddToViewEvent;
41  import javax.faces.flow.FlowHandler;
42  import javax.faces.lifecycle.ClientWindow;
43  import javax.faces.lifecycle.Lifecycle;
44  import javax.faces.lifecycle.LifecycleFactory;
45  import javax.faces.render.RenderKit;
46  import javax.faces.render.RenderKitFactory;
47  import javax.faces.render.ResponseStateManager;
48  import javax.faces.view.ViewDeclarationLanguage;
49  import javax.faces.view.ViewMetadata;
50  import javax.faces.webapp.FacesServlet;
51  import javax.servlet.http.HttpServletResponse;
52  import org.apache.myfaces.event.PostClientWindowAndViewInitializedEvent;
53  
54  import org.apache.myfaces.renderkit.ErrorPageWriter;
55  import org.apache.myfaces.shared.config.MyfacesConfig;
56  import org.apache.myfaces.shared.util.ExternalContextUtils;
57  import org.apache.myfaces.shared.util.ViewProtectionUtils;
58  
59  /**
60   * Implements the Restore View Phase (JSF Spec 2.2.1)
61   * 
62   * @author Nikolay Petrov (latest modification by $Author$)
63   * @author Bruno Aranda (JSF 1.2)
64   * @version $Revision$ $Date$
65   */
66  class RestoreViewExecutor extends PhaseExecutor
67  {
68  
69      //private static final Log log = LogFactory.getLog(RestoreViewExecutor.class);
70      private static final Logger log = Logger.getLogger(RestoreViewExecutor.class.getName());
71      
72      private RestoreViewSupport _restoreViewSupport;
73      
74      private Boolean _viewNotFoundCheck;
75      
76      private RenderKitFactory _renderKitFactory = null;
77      
78      @Override
79      public void doPrePhaseActions(FacesContext facesContext)
80      {
81          // Call initView() on the ViewHandler. 
82          // This will set the character encoding properly for this request.
83          // Note that we are doing this here, because we need the character encoding
84          // to be set as early as possible (before any PhaseListener is executed).
85          facesContext.getApplication().getViewHandler().initView(facesContext);
86      }
87  
88      public boolean execute(FacesContext facesContext)
89      {
90          if (facesContext == null)
91          {
92              throw new FacesException("FacesContext is null");
93          }
94  
95          // get some required Objects
96          Application application = facesContext.getApplication();
97          ViewHandler viewHandler = application.getViewHandler();
98          UIViewRoot viewRoot = facesContext.getViewRoot();
99          RestoreViewSupport restoreViewSupport = getRestoreViewSupport(facesContext);
100 
101         // Examine the FacesContext instance for the current request. If it already contains a UIViewRoot
102         if (viewRoot != null)
103         {
104             if (log.isLoggable(Level.FINEST))
105             {
106                 log.finest("View already exists in the FacesContext");
107             }
108             
109             // Set the locale on this UIViewRoot to the value returned by the getRequestLocale() method on the
110             // ExternalContext for this request
111             viewRoot.setLocale(facesContext.getExternalContext().getRequestLocale());
112             
113             restoreViewSupport.processComponentBinding(facesContext, viewRoot);
114             
115             // invoke the afterPhase MethodExpression of UIViewRoot
116             _invokeViewRootAfterPhaseListener(facesContext);
117             
118             return false;
119         }
120         
121         String viewId = restoreViewSupport.calculateViewId(facesContext);
122 
123         // Determine if the current request is an attempt by the 
124         // servlet container to display an error page.
125         // If the request is an error page request, the servlet container
126         // is required to set the request parameter "javax.servlet.error.message".
127         final boolean errorPageRequest = facesContext.getExternalContext().getRequestMap()
128                                                  .get("javax.servlet.error.message") != null;
129         
130         // Determine if this request is a postback or an initial request.
131         // But if it is an error page request, do not treat it as a postback (since 2.0)
132         if (!errorPageRequest && restoreViewSupport.isPostback(facesContext))
133         { // If the request is a postback
134             if (log.isLoggable(Level.FINEST))
135             {
136                 log.finest("Request is a postback");
137             }
138 
139             if (checkViewNotFound(facesContext))
140             {
141                 String derivedViewId = viewHandler.deriveLogicalViewId(facesContext, viewId);
142                 ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, 
143                     derivedViewId);
144             
145                 // viewHandler.deriveLogicalViewId() could trigger an InvalidViewIdException, which
146                 // it is handled internally sending a 404 error code set the response as complete.
147                 if (facesContext.getResponseComplete())
148                 {
149                     return true;
150                 }
151             
152                 if (vdl == null || derivedViewId == null)
153                 {
154                     sendSourceNotFound(facesContext, viewId);
155                     return true;
156                 }
157                 else if (!restoreViewSupport.checkViewExists(facesContext, derivedViewId))
158                 {
159                     sendSourceNotFound(facesContext, viewId);
160                     return true;
161                 }
162             }
163             
164             try
165             {
166                 facesContext.setProcessingEvents(false);
167                 // call ViewHandler.restoreView(), passing the FacesContext instance for the current request and the 
168                 // view identifier, and returning a UIViewRoot for the restored view.
169                 
170                 viewRoot = viewHandler.restoreView(facesContext, viewId);
171                 if (viewRoot == null)
172                 {
173                     if (facesContext.getResponseComplete())
174                     {
175                         // If the view handler cannot restore the view and the response
176                         // is complete, it can be an error or some logic in restoreView.
177                         return true;
178                     }
179                     else
180                     {
181                         // If the return from ViewHandler.restoreView() is null, throw a ViewExpiredException with an 
182                         // appropriate error message.
183                         throw new ViewExpiredException("No saved view state could be found for the view identifier: "
184                                                    + viewId, viewId);
185                     }
186                 }
187                 // If the view is transient (stateless), it is necessary to check the view protection
188                 // in POST case.
189                 if (viewRoot.isTransient())
190                 {
191                     checkViewProtection(facesContext, viewHandler, viewRoot.getViewId(), viewRoot);
192                 }
193                 
194                 // Store the restored UIViewRoot in the FacesContext.
195                 facesContext.setViewRoot(viewRoot);
196             }
197             finally
198             {
199                 facesContext.setProcessingEvents(true);
200             }
201             
202             // Restore binding
203             // See https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=806
204             restoreViewSupport.processComponentBinding(facesContext, viewRoot);
205             
206             ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
207             if (clientWindow != null)
208             {
209                 // The idea of this event is once the client window and the view is initialized, 
210                 // you have the information required to clean up any scope that belongs to old
211                 // clientWindow instances like flow scope (in server side state saving).
212                 facesContext.getApplication().publishEvent(facesContext, PostClientWindowAndViewInitializedEvent.class, 
213                         clientWindow);
214             }
215         }
216         else
217         { // If the request is a non-postback
218             if (log.isLoggable(Level.FINEST))
219             {
220                 log.finest("Request is not a postback. New UIViewRoot will be created");
221             }
222             
223             //viewHandler.deriveViewId(facesContext, viewId)
224             //restoreViewSupport.deriveViewId(facesContext, viewId)
225             String logicalViewId = viewHandler.deriveLogicalViewId(facesContext, viewId);
226             ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, logicalViewId);
227             
228             // viewHandler.deriveLogicalViewId() could trigger an InvalidViewIdException, which
229             // it is handled internally sending a 404 error code set the response as complete.
230             if (facesContext.getResponseComplete())
231             {
232                 return true;
233             }
234             
235             if (checkViewNotFound(facesContext))
236             {
237                 if (vdl == null || logicalViewId == null)
238                 {
239                     sendSourceNotFound(facesContext, viewId);
240                     return true;
241                 }
242                 else if (!restoreViewSupport.checkViewExists(facesContext, logicalViewId))
243                 {
244                     sendSourceNotFound(facesContext, viewId);
245                     return true;
246                 }
247             }
248             
249             if (vdl != null)
250             {
251                 ViewMetadata metadata = vdl.getViewMetadata(facesContext, viewId);
252                 
253                 if (metadata != null)
254                 {
255                     viewRoot = metadata.createMetadataView(facesContext);
256                     
257                     if(facesContext.getResponseComplete())
258                     {
259                         // this can happen if the current request is a debug request,
260                         // in this case no further processing is necessary
261                         return true;
262                     }
263                 }
264     
265                 if (viewRoot == null)
266                 {
267                     facesContext.renderResponse();
268                 }
269                 else if (viewRoot != null && !ViewMetadata.hasMetadata(viewRoot))
270                 {
271                     facesContext.renderResponse();
272                 }
273             }
274             else
275             {
276                 // Call renderResponse
277                 facesContext.renderResponse();
278             }
279 
280             checkViewProtection(facesContext, viewHandler, logicalViewId, viewRoot);
281             
282             // viewRoot can be null here, if ...
283             //   - we don't have a ViewDeclarationLanguage (e.g. when using facelets-1.x)
284             //   - there is no view metadata or metadata.createMetadataView() returned null
285             if (viewRoot == null)
286             {
287                 // call ViewHandler.createView(), passing the FacesContext instance for the current request and 
288                 // the view identifier
289                 viewRoot = viewHandler.createView(facesContext, viewId);
290             }
291             
292             if (viewRoot == null && facesContext.getResponseComplete())
293             {
294                 // If the view handler cannot create the view and the response
295                 // is complete, it can be an error, just get out of the algorithm.
296                 return true;
297             }
298             
299             // Subscribe the newly created UIViewRoot instance to the AfterAddToParent event, passing the 
300             // UIViewRoot instance itself as the listener.
301             // -= Leonardo Uribe =- This line it is not necessary because it was
302             // removed from jsf 2.0 section 2.2.1 when pass from EDR2 to Public Review 
303             // viewRoot.subscribeToEvent(PostAddToViewEvent.class, viewRoot);
304             
305             // Store the new UIViewRoot instance in the FacesContext.
306             facesContext.setViewRoot(viewRoot);
307 
308             ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
309             if (clientWindow != null)
310             {
311                 facesContext.getApplication().publishEvent(facesContext, PostClientWindowAndViewInitializedEvent.class, 
312                         clientWindow);
313             }
314             
315             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
316             if (flowHandler != null)
317             {
318                 flowHandler.clientWindowTransition(facesContext);
319             }
320 
321             // Publish an AfterAddToParent event with the created UIViewRoot as the event source.
322             application.publishEvent(facesContext, PostAddToViewEvent.class, viewRoot);
323         }
324 
325         // add the ErrorPageBean to the view map to fully support 
326         // facelet error pages, if we are in ProjectStage Development
327         // and currently generating an error page
328         if (errorPageRequest && facesContext.isProjectStage(ProjectStage.Development))
329         {
330             facesContext.getViewRoot().getViewMap()
331                     .put(ErrorPageWriter.ERROR_PAGE_BEAN_KEY, new ErrorPageWriter.ErrorPageBean());
332         }
333         
334         // invoke the afterPhase MethodExpression of UIViewRoot
335         _invokeViewRootAfterPhaseListener(facesContext);
336         
337         return false;
338     }
339     
340     private void checkViewProtection(FacesContext facesContext, ViewHandler viewHandler, 
341         String viewId, UIViewRoot root) throws ProtectedViewException
342     {
343         boolean valid = true;
344         if (ViewProtectionUtils.isViewProtected(facesContext, viewId))
345         {
346             // "... Obtain the value of the value of the request parameter whose 
347             // name is given by the value of ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM. 
348             // If there is no value, throw ProtectedViewException ..."
349             String token = (String) facesContext.getExternalContext().getRequestParameterMap().get(
350                 ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM);
351             if (token != null && token.length() > 0)
352             {
353                 String renderKitId = null;
354                 if (root != null)
355                 {
356                     renderKitId = root.getRenderKitId();
357                 }
358                 if (renderKitId == null)
359                 {
360                     renderKitId = viewHandler.calculateRenderKitId(facesContext);
361                 }
362                 RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
363                 ResponseStateManager rsm = renderKit.getResponseStateManager();
364                 
365                 String storedToken = rsm.getCryptographicallyStrongTokenFromSession(facesContext);
366                 if (token.equals(storedToken))
367                 {
368                     if (!ExternalContextUtils.isPortlet(facesContext.getExternalContext()))
369                     {
370                         // Any check beyond this point only has sense for servlet requests.
371                         String referer = facesContext.getExternalContext().
372                             getRequestHeaderMap().get("Referer");
373                         if (referer != null)
374                         {
375                             valid = valid && checkRefererOrOriginHeader(
376                                 facesContext, viewHandler, referer);
377                         }
378                         String origin = facesContext.getExternalContext().
379                             getRequestHeaderMap().get("Origin");
380                         if (valid && origin != null)
381                         {
382                             valid = valid && checkRefererOrOriginHeader(
383                                 facesContext, viewHandler, origin);
384                         }
385                     }
386                 }
387                 else
388                 {
389                     valid = false;
390                 }
391             }
392             else
393             {
394                 valid = false;
395             }
396         }
397         if (!valid)
398         {
399             throw new ProtectedViewException();
400         }
401     }
402     
403     private boolean checkRefererOrOriginHeader(FacesContext facesContext, 
404         ViewHandler viewHandler, String refererOrOrigin)
405     {
406         try
407         {
408             // The referer can be absolute or relative. 
409             ExternalContext ectx = facesContext.getExternalContext();
410             URI refererURI = new URI(refererOrOrigin);
411             String path = refererURI.getPath();
412             String appContextPath = ectx.getApplicationContextPath();
413             if (refererURI.isAbsolute())
414             {
415                 // Check if the referer comes from the same host
416                 String host = refererURI.getHost();
417                 int port = refererURI.getPort();
418                 String serverHost = ectx.getRequestServerName();
419                 int serverPort = ectx.getRequestServerPort();
420 
421                 boolean matchPort = true;
422                 if (serverPort != -1 && port != -1)
423                 {
424                     matchPort = (serverPort == port);
425                 }
426                 boolean isStrictJsf2OriginHeaderAppPath = 
427                                 MyfacesConfig.getCurrentInstance(ectx).isStrictJsf2OriginHeaderAppPath();
428                 if (!path.equals(""))
429                 {
430                     if (serverHost.equals(host) && matchPort && path.contains(appContextPath))
431                     {
432                         // Referer Header match
433                     }
434                     else
435                     {
436                         // Referer Header does not match
437                         return false;
438                     }
439                 }
440                 else
441                 {
442                     if (serverHost.equals(host) && matchPort && !isStrictJsf2OriginHeaderAppPath)
443                     {
444                         // Origin Header match and 
445                         // STRICT_JSF_2_ORIGIN_HEADER_APP_PATH property is set to false (default)
446                         // Because we don't want to strictly follow JSF 2.x spec
447                     }
448                     else
449                     {
450                         // Origin Header does not match
451                         return false;
452                     }
453                 }
454             }
455             // In theory path = appContextPath + servletPath + pathInfo. 
456             int appContextPathIndex = appContextPath != null ? path.indexOf(appContextPath) : -1;
457             int servletPathIndex = -1;
458             int pathInfoIndex = -1;
459             if (ectx.getRequestServletPath() != null && ectx.getRequestPathInfo() != null)
460             {
461                 servletPathIndex = ectx.getRequestServletPath() != null ? 
462                     path.indexOf(ectx.getRequestServletPath(), 
463                                  appContextPathIndex >= 0 ? appContextPathIndex : 0) : -1;
464                 if (servletPathIndex != -1)
465                 {
466                     pathInfoIndex = servletPathIndex + ectx.getRequestServletPath().length();
467                 }
468             }
469             else
470             {
471                 servletPathIndex = -1;
472                 pathInfoIndex = (appContextPathIndex >= 0 ? appContextPathIndex : 0) + appContextPath.length();
473             }
474 
475             // If match appContextPath(if any) and match servletPath or pathInfo referer header is ok
476             if ((appContextPath == null || appContextPathIndex >= 0) && 
477                 (servletPathIndex >= 0 || pathInfoIndex >= 0))
478             {
479                 String refererViewId;
480                 if (pathInfoIndex >= 0)
481                 {
482                     refererViewId = path.substring(pathInfoIndex);
483                 }
484                 else
485                 {
486                     refererViewId = path.substring(servletPathIndex);
487                 }
488                 
489                 String logicalViewId = viewHandler.deriveViewId(facesContext, refererViewId);
490                     
491                 // If the header is present, use the protected view API to determine if any of
492                 // the declared protected views match the value of the Referer header.
493                 // - If so, conclude that the previously visited page is also a protected 
494                 //   view and it is therefore safe to continue.
495                 // - Otherwise, try to determine if the value of the Referer header corresponds 
496                 //   to any of the views in the current web application.
497                 // -= Leonardo Uribe =- All views that are protected also should exists!. the
498                 // only relevant check is use ViewHandler.deriveViewId(...) here. Check if the
499                 // view is protected here is not necessary.
500                 if (logicalViewId != null)
501                 {
502                     return true;
503                 }
504                 else
505                 {
506                     // View do not exists
507                 }
508             }
509             return true;
510         }
511         catch (URISyntaxException ex)
512         {
513             return false;
514         }
515     }
516     
517     /**
518      * Invoke afterPhase MethodExpression of UIViewRoot.
519      * Note: In this phase it is not possible to invoke the beforePhase method, because we
520      * first have to restore the view to get its attributes. Also it is not really possible
521      * to call the afterPhase method inside of UIViewRoot for this phase, thus it was decided
522      * in the JSF 2.0 spec rev A to put this here.
523      * @param facesContext
524      */
525     private void _invokeViewRootAfterPhaseListener(FacesContext facesContext)
526     {
527         // get the UIViewRoot (note that it must not be null at this point)
528         UIViewRoot root = facesContext.getViewRoot();
529         MethodExpression afterPhaseExpression = root.getAfterPhaseListener();
530         if (afterPhaseExpression != null)
531         {
532             PhaseEvent event = new PhaseEvent(facesContext, getPhase(), _getLifecycle(facesContext));
533             try
534             {
535                 afterPhaseExpression.invoke(facesContext.getELContext(), new Object[] { event });
536             }
537             catch (Throwable t) 
538             {
539                 log.log(Level.SEVERE, "An Exception occured while processing " +
540                         afterPhaseExpression.getExpressionString() + 
541                         " in Phase " + getPhase(), t);
542             }
543         }
544     }
545     
546     /**
547      * Gets the current Lifecycle instance from the LifecycleFactory
548      * @param facesContext
549      * @return
550      */
551     private Lifecycle _getLifecycle(FacesContext facesContext)
552     {
553         LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
554         String id = facesContext.getExternalContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
555         if (id == null)
556         {
557             id = LifecycleFactory.DEFAULT_LIFECYCLE;
558         }
559         return factory.getLifecycle(id);  
560     }
561     
562     protected RestoreViewSupport getRestoreViewSupport()
563     {
564         return getRestoreViewSupport(FacesContext.getCurrentInstance());
565     }
566     
567     protected RestoreViewSupport getRestoreViewSupport(FacesContext context)
568     {
569         if (_restoreViewSupport == null)
570         {
571             _restoreViewSupport = new DefaultRestoreViewSupport(context);
572         }
573         return _restoreViewSupport;
574     }
575 
576     protected RenderKitFactory getRenderKitFactory()
577     {
578         if (_renderKitFactory == null)
579         {
580             _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
581         }
582         return _renderKitFactory;
583     }
584     
585     /**
586      * @param restoreViewSupport
587      *            the restoreViewSupport to set
588      */
589     public void setRestoreViewSupport(RestoreViewSupport restoreViewSupport)
590     {
591         _restoreViewSupport = restoreViewSupport;
592     }
593 
594     public PhaseId getPhase()
595     {
596         return PhaseId.RESTORE_VIEW;
597     }
598     
599     protected boolean checkViewNotFound(FacesContext facesContext)
600     {
601         if (_viewNotFoundCheck == null)
602         {
603             
604             _viewNotFoundCheck = MyfacesConfig.getCurrentInstance(
605                 facesContext.getExternalContext()).isStrictJsf2ViewNotFound();
606         }
607         return _viewNotFoundCheck;
608     }
609     
610     private void sendSourceNotFound(FacesContext context, String message)
611     {
612         HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
613         try
614         {
615             context.responseComplete();
616             response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
617         }
618         catch (IOException ioe)
619         {
620             throw new FacesException(ioe);
621         }
622     }
623 }